From 869e74d6fd67ed4cab9bf8f21325d71a6661b903 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Mon, 24 Apr 2023 19:18:04 +0300 Subject: [PATCH 001/115] code improvments --- .../Integrations/Tenable_sc/Tenable_sc.py | 1305 ++++++++--------- .../Integrations/Tenable_sc/Tenable_sc.yml | 6 + 2 files changed, 602 insertions(+), 709 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index e6294659fe24..6ce8759b0f6e 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -7,43 +7,13 @@ import json from datetime import datetime from requests import cookies - +from typing import Dict, Any # disable insecure warnings urllib3.disable_warnings() -if not demisto.params().get('proxy', False): - del os.environ['HTTP_PROXY'] - del os.environ['HTTPS_PROXY'] - del os.environ['http_proxy'] - del os.environ['https_proxy'] ''' GLOBAL VARIABLES''' -USERNAME = demisto.params()['credentials']['identifier'] -PASSWORD = demisto.params()['credentials']['password'] -VERIFY_SSL = not demisto.params().get('unsecure', False) - -MAX_REQUEST_RETRIES = 3 - -FETCH_TIME_DEFAULT = '3 days' -FETCH_TIME = demisto.params().get('fetch_time', FETCH_TIME_DEFAULT) -FETCH_TIME = FETCH_TIME if FETCH_TIME and FETCH_TIME.strip() else FETCH_TIME_DEFAULT - -SESSION = Session() -TOKEN = demisto.getIntegrationContext().get('token') -COOKIE = demisto.getIntegrationContext().get('cookie') - - -def get_server_url(): - url = demisto.params()['server'] - url = re.sub('/[\/]+$/', '', url) - url = re.sub('\/$', '', url) - return url - - -BASE_URL = get_server_url() -SERVER_URL = BASE_URL + '/rest' - ACTION_TYPE_TO_VALUE = { 'notification': 'users.username', 'email': 'users.username', @@ -53,95 +23,435 @@ def get_server_url(): 'ticket': 'assignee.username' } -''' HELPER FUNCTIONS ''' +class Client: + def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str = "", + password: str = "", access_key: str = "", secret_key: str = "", url: str = "",): + self.verify_ssl = verify_ssl + self.max_retries = 3 + self.session = Session() + self.url = get_server_url(url) + '/rest' + self.access_key = access_key + self.secret_key = secret_key + self.user_name = user_name + self.password = password + + self.headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } + if not proxy: + del os.environ['HTTP_PROXY'] + del os.environ['HTTPS_PROXY'] + del os.environ['http_proxy'] + del os.environ['https_proxy'] + integration_context = demisto.getIntegrationContext() + self.token = integration_context.get('token') + self.cookie = integration_context.get('cookie') + + def send_request(self, path, method='get', body=None, params=None, headers=None, try_number=1): + body = body if body is not None else {} + params = params if params is not None else {} + headers = headers if headers is not None else self.headers + + headers['X-SecurityCenter'] = self.token + url = '{}/{}'.format(self.url, path) + + session_cookie = cookies.create_cookie('TNS_SESSIONID', self.cookie) + self.session.cookies.set_cookie(session_cookie) # type: ignore + + res = self.session.request(method, url, data=json.dumps(body), params=params, headers=headers, verify=self.verify_ssl) + + if res.status_code == 403 and try_number <= self.max_retries: + self.login() + headers['X-SecurityCenter'] = self.token # The Token is being updated in the login + return self.send_request(path, method, body, params, headers, try_number + 1) + + elif res.status_code < 200 or res.status_code >= 300: + try: + error = res.json() + except Exception: + # type: ignore + return_error( + f'Error: Got status code {str(res.status_code)} with {url=} \ + with body {res.content} with headers {str(res.headers)}') # type: ignore + + return_error(f"Error: Got an error from TenableSC, code: {error['error_code']}, \ + details: {error['error_msg']}") # type: ignore + return res.json() + + def login(self): + login_body = { + 'username': self.user_name, + 'password': self.password + } + login_response = self.send_login_request(login_body) -def send_request(path, method='get', body=None, params=None, headers=None, try_number=1): - body = body if body is not None else {} - params = params if params is not None else {} - headers = headers if headers is not None else get_headers() + if 'response' not in login_response: + return_error('Error: Could not retrieve login token') - headers['X-SecurityCenter'] = TOKEN - url = '{}/{}'.format(SERVER_URL, path) + token = login_response['response'].get('token') + # There might be a case where the API does not return a token because there are too many sessions with the same user + # In that case we need to add 'releaseSession = true' + if not token: + login_body['releaseSession'] = 'true' + login_response = self.send_login_request(login_body) + if 'response' not in login_response or 'token' not in login_response['response']: + return_error('Error: Could not retrieve login token') + token = login_response['response']['token'] - session_cookie = cookies.create_cookie('TNS_SESSIONID', COOKIE) - SESSION.cookies.set_cookie(session_cookie) # type: ignore + self.token = str(token) + demisto.setIntegrationContext({'token': token}) - res = SESSION.request(method, url, data=json.dumps(body), params=params, headers=headers, verify=VERIFY_SSL) + def send_login_request(self, login_body): + path = 'token' + url = '{}/{}'.format(self.url, path) - if res.status_code == 403 and try_number <= MAX_REQUEST_RETRIES: - login() - headers['X-SecurityCenter'] = TOKEN # The Token is being updated in the login - return send_request(path, method, body, params, headers, try_number + 1) + headers = self.headers + res = self.session.request('post', url, headers=headers, data=json.dumps(login_body), verify=self.verify_ssl) - elif res.status_code < 200 or res.status_code >= 300: - try: - error = res.json() - except Exception: - # type: ignore - return_error( - f'Error: Got status code {str(res.status_code)} with {url=} \ - with body {res.content} with headers {str(res.headers)}') # type: ignore + if res.status_code < 200 or res.status_code >= 300: + return_error(f'Error: Got status code {str(res.status_code)} with {url=} \ + with body {res.content} with headers {str(res.headers)}') # type: ignore - return_error(f"Error: Got an error from TenableSC, code: {error['error_code']}, \ - details: {error['error_msg']}") # type: ignore - return res.json() + cookie = res.cookies.get('TNS_SESSIONID', self.cookie) + demisto.setIntegrationContext({'cookie': cookie}) + return res.json() -def get_headers(): - headers = { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } + def logout(self): + self.send_request(path='token', method='delete') - return headers + def create_scan(self, name, repo_id, policy_id, plugin_id, description, zone_id, schedule, asset_ids, + ips, scan_virtual_hosts, report_ids, credentials, timeout_action, max_scan_time, + dhcp_track, rollover_type, dependent): + path = 'scan' + scan_type = 'policy' if policy_id else 'plugin' -def send_login_request(login_body): - path = 'token' - url = '{}/{}'.format(SERVER_URL, path) + body = { + 'name': name, + 'type': scan_type, + 'repository': { + 'id': repo_id + } + } - headers = get_headers() - res = SESSION.request('post', url, headers=headers, data=json.dumps(login_body), verify=VERIFY_SSL) + if policy_id: + body['policy'] = { + 'id': policy_id + } - if res.status_code < 200 or res.status_code >= 300: - return_error(f'Error: Got status code {str(res.status_code)} with {url=} \ - with body {res.content} with headers {str(res.headers)}') # type: ignore + if plugin_id: + body['pluginID'] = plugin_id - global COOKIE - COOKIE = res.cookies.get('TNS_SESSIONID', COOKIE) - demisto.setIntegrationContext({'cookie': COOKIE}) + if description: + body['description'] = description - return res.json() + if zone_id: + body['zone'] = { + 'id': zone_id + } + if dhcp_track: + body['dhcpTracking'] = dhcp_track -def login(): - login_body = { - 'username': USERNAME, - 'password': PASSWORD - } - login_response = send_login_request(login_body) - - if 'response' not in login_response: - return_error('Error: Could not retrieve login token') - - token = login_response['response'].get('token') - # There might be a case where the API does not return a token because there are too many sessions with the same user - # In that case we need to add 'releaseSession = true' - if not token: - login_body['releaseSession'] = 'true' - login_response = send_login_request(login_body) - if 'response' not in login_response or 'token' not in login_response['response']: - return_error('Error: Could not retrieve login token') - token = login_response['response']['token'] + if schedule: + body['schedule'] = { + 'type': schedule + } + + if dependent: + body['schedule']['dependentID'] = dependent + + if report_ids: + body['reports'] = [{'id': r_id, 'reportSource': 'individual'} for r_id in argToList(report_ids)] + + if asset_ids: + if str(asset_ids).startswith('All'): + manageable = True if asset_ids == 'AllManageable' else False + res = self.get_assets(None) + assets = get_elements(res['response'], manageable) + asset_ids = list(map(lambda a: a['id'], assets)) + body['assets'] = [{'id': a_id} for a_id in argToList(asset_ids)] + + if credentials: + body['credentials'] = [{'id': c_id} for c_id in argToList(credentials)] + + if timeout_action: + body['timeoutAction'] = timeout_action + + if scan_virtual_hosts: + body['scanningVirtualHosts'] = scan_virtual_hosts + + if rollover_type: + body['rolloverType'] = rollover_type + + if ips: + body['ipList'] = ips + + if max_scan_time: + body['maxScanTime'] = max_scan_time * 3600 + + return self.send_request(path, method='post', body=body) + + def get_scan_results(self, scan_results_id): + path = 'scanResult/' + scan_results_id + + return self.send_request(path) + + def launch_scan(self, scan_id, scan_target): + path = 'scan/' + scan_id + '/launch' + body = None + if scan_target: + body = { + 'diagnosticTarget': scan_target['address'], + 'diagnosticPassword': scan_target['password'] + } + + return self.send_request(path, 'post', body=body) + + def get_query(self, query_id): + path = 'query/' + query_id + + return self.send_request(path) + + def get_all_scan_results(self): + params = { + 'fields': 'name,description,details,status,scannedIPs,startTime,scanDuration,importStart,' + 'finishTime,completedChecks,owner,ownerGroup,repository' + } + return self.send_request(path='scanResult', params=params) + + def get_alerts(self, fields=None, alert_id=None): + path = 'alert' + params = {} # type: Dict[str, Any] + + if alert_id: + path += '/' + alert_id + + if fields: + params = { + 'fields': fields + } + + return self.send_request(path, params=params) + + def get_system_licensing(self): + return self.send_request(path='status') + + def get_scans(self, fields): + params = None + + if fields: + params = { + 'fields': fields + } + + return self.send_request(path='scan', params=params) + + def get_policies(self, fields): + params = None + + if fields: + params = { + 'fields': fields + } + + return self.send_request(path='policy', params=params) + + def get_repositories(self): + return self.send_request(path='repository') + + def get_assets(self, fields): + params = None + + if fields: + params = { + 'fields': fields + } + + return self.send_request(path='asset', params=params) + + def get_credentials(self, fields): + params = None + + if fields: + params = { + 'fields': fields + } + + return self.send_request(path='credential', params=params) + + def get_asset(self, asset_id): + params = { + 'fields': 'id,name,description,status,createdTime,modifiedTime,viewableIPs,ownerGroup,tags,owner' + } + + return self.send_request(path=f'asset/{asset_id}', params=params) + + def create_asset(self, name, description, owner_id, tags, ips): + body = { + 'name': name, + 'definedIPs': ips, + 'type': 'static' + } + + if description: + body['description'] = description + + if owner_id: + body['ownerID'] = owner_id + + if tags: + body['tags'] = tags + + return self.send_request(path='asset', method='post', body=body) + + def delete_asset(self, asset_id): + return self.send_request(path=f'asset/{asset_id}', method='delete') + + def get_report_definitions(self, fields): + params = None + + if fields: + params = { + 'fields': fields + } + + return self.send_request(path='reportDefinition', params=params) + + def get_zones(self): + return self.send_request(path='zone') + + def get_scan_report(self, scan_results_id): + path = 'scanResult/' + scan_results_id + + params = { + 'fields': 'name,description,details,status,scannedIPs,progress,startTime,scanDuration,importStart,' + 'finishTime,completedChecks,owner,ownerGroup,repository,policy' + } + + return self.send_request(path, params=params) + + def create_query(self, scan_id, tool, query_filters=None): + path = 'query' - global TOKEN - TOKEN = str(token) - demisto.setIntegrationContext({'token': TOKEN}) + body = { + 'name': 'scan ' + scan_id + ' query', + 'type': 'vuln', + 'tool': tool, + 'scanID': scan_id + } + + if query_filters: + body['filters'] = query_filters + + return self.send_request(path, method='post', body=body) + + def delete_query(self, query_id): + if not query_id: + return_error('query id returned None') + path = 'query/' + str(query_id) + self.send_request(path, method='delete') + + def get_analysis(self, query, scan_results_id): + path = 'analysis' + + # This function can receive 'query' argument either as a dict (as in get_vulnerability_command), + # or as an ID of an existing query (as in get_vulnerabilities). + # Here we form the query field in the request body as a dict, as required. + if not isinstance(query, dict): + query = {'id': query} + + body = { + 'type': 'vuln', + 'query': query, + 'sourceType': 'individual', + 'scanID': scan_results_id, + 'view': 'all' + } + + return self.send_request(path, method='post', body=body) + + def list_plugins(self, name, plugin_type, cve): + params = { + 'fields': 'id,type,name,description,family' + } + + if cve: + params['filterField'] = 'xrefs:CVE' + params['op'] = 'eq' + params['value'] = cve + + if plugin_type: + params['type'] = plugin_type + + return self.send_request(path='plugin', params=params) + + def get_system_diagnostics(self): + return self.send_request(path='system/diagnostics') + + def get_system(self): + return self.send_request(path='system') + + def change_scan_status(self, scan_results_id, status): + path = 'scanResult/' + scan_results_id + '/' + status + return self.send_request(path, method='post') -def logout(): - send_request(path='token', method='delete') + def get_vulnerability(self, vuln_id): + path = 'plugin/' + vuln_id + + params = { + 'fields': 'name,description,family,type,cpe,riskFactor,solution,synopsis,exploitEase,exploitAvailable,' + 'cvssVector,baseScore,pluginPubDate,pluginModDate,vulnPubDate,temporalScore,xrefs,checkType' + } + + return self.send_request(path, params=params) + + def delete_scan(self, scan_id): + return self.send_request(path=f'scan/{scan_id}', method='delete') + + def get_device(self, uuid, ip, dns_name, repo): + path = 'repository/' + repo + '/' if repo else '' + path += 'deviceInfo' + params = { + 'fields': 'ip,uuid,macAddress,netbiosName,dnsName,os,osCPE,lastScan,repository,total,severityLow,' + 'severityMedium,severityHigh,severityCritical' + } + if uuid: + params['uuid'] = uuid + else: + params['ip'] = ip + if dns_name: + params['dnsName'] = dns_name + + return self.send_request(path, params=params) + + def get_users(self, fields, user_id): + path = 'user' + + if user_id: + path += '/' + user_id + + params = None + + if fields: + params = { + 'fields': fields + } + + return self.send_request(path, params=params) + + +''' HELPER FUNCTIONS ''' + + +def get_server_url(url): + url = re.sub('/[\/]+$/', '', url) + url = re.sub('\/$', '', url) + return url def return_message(msg): @@ -152,9 +462,9 @@ def return_message(msg): ''' FUNCTIONS ''' -def list_scans_command(): - res = get_scans('id,name,description,policy,ownerGroup,owner') - manageable = demisto.args().get('manageable', 'false').lower() +def list_scans_command(client: Client, args: Dict[str, Any]): + res = client.get_scans('id,name,description,policy,ownerGroup,owner') + manageable = args.get('manageable', 'false').lower() if not res or 'response' not in res or not res['response']: return_message('No scans found') @@ -187,22 +497,10 @@ def list_scans_command(): }) -def get_scans(fields): - path = 'scan' - params = None - - if fields: - params = { - 'fields': fields - } - - return send_request(path, params=params) - - -def list_policies_command(): - res = get_policies('id,name,description,tags,modifiedTime,owner,ownerGroup,policyTemplate') +def list_policies_command(client: Client, args: Dict[str, Any]): + res = client.get_policies('id,name,description,tags,modifiedTime,owner,ownerGroup,policyTemplate') - manageable = demisto.args().get('manageable', 'false').lower() + manageable = args.get('manageable', 'false').lower() if not res or 'response' not in res or not res['response']: return_message('No policies found') @@ -237,20 +535,8 @@ def list_policies_command(): }) -def get_policies(fields): - path = 'policy' - params = None - - if fields: - params = { - 'fields': fields - } - - return send_request(path, params=params) - - -def list_repositories_command(): - res = get_repositories() +def list_repositories_command(client: Client, args: Dict[str, Any]): + res = client.get_repositories() if not res or 'response' not in res or not res['response']: return_message('No repositories found') @@ -280,16 +566,10 @@ def list_repositories_command(): }) -def get_repositories(): - path = 'repository' - - return send_request(path) +def list_credentials_command(client: Client, args: Dict[str, Any]): + res = client.get_credentials('id,name,description,type,ownerGroup,owner,tags,modifiedTime') - -def list_credentials_command(): - res = get_credentials('id,name,description,type,ownerGroup,owner,tags,modifiedTime') - - manageable = demisto.args().get('manageable', 'false').lower() + manageable = args.get('manageable', 'false').lower() if not res or 'response' not in res or not res['response']: return_message('No credentials found') @@ -324,22 +604,10 @@ def list_credentials_command(): }) -def get_credentials(fields): - path = 'credential' - params = None - - if fields: - params = { - 'fields': fields - } - - return send_request(path, params=params) - - -def list_assets_command(): - res = get_assets('id,name,description,ipCount,type,tags,modifiedTime,groups,owner') +def list_assets_command(client: Client, args: Dict[str, Any]): + res = client.get_assets('id,name,description,ipCount,type,tags,modifiedTime,groups,owner') - manageable = demisto.args().get('manageable', 'false').lower() + manageable = args.get('manageable', 'false').lower() if not res or 'response' not in res or not res['response']: return_message('No assets found') @@ -374,22 +642,10 @@ def list_assets_command(): }) -def get_assets(fields): - path = 'asset' - params = None - - if fields: - params = { - 'fields': fields - } - - return send_request(path, params=params) - +def get_asset_command(client: Client, args: Dict[str, Any]): + asset_id = args.get('asset_id') -def get_asset_command(): - asset_id = demisto.args()['asset_id'] - - res = get_asset(asset_id) + res = client.get_asset(asset_id) if not res or 'response' not in res: return_message('Asset not found') @@ -429,24 +685,14 @@ def get_asset_command(): }) -def get_asset(asset_id): - path = 'asset/' + asset_id - - params = { - 'fields': 'id,name,description,status,createdTime,modifiedTime,viewableIPs,ownerGroup,tags,owner' - } - - return send_request(path, params=params) - +def create_asset_command(client: Client, args: Dict[str, Any]): + name = args.get('name') + description = args.get('description') + owner_id = args.get('owner_id') + tags = args.get('tags') + ips = args.get('ip_list') -def create_asset_command(): - name = demisto.args()['name'] - description = demisto.args().get('description') - owner_id = demisto.args().get('owner_id') - tags = demisto.args().get('tags') - ips = demisto.args().get('ip_list') - - res = create_asset(name, description, owner_id, tags, ips) + res = client.create_asset(name, description, owner_id, tags, ips) if not res or 'response' not in res: return_error('Error: Could not retrieve the asset') @@ -479,31 +725,10 @@ def create_asset_command(): }) -def create_asset(name, description, owner_id, tags, ips): - path = 'asset' - - body = { - 'name': name, - 'definedIPs': ips, - 'type': 'static' - } - - if description: - body['description'] = description - - if owner_id: - body['ownerID'] = owner_id - - if tags: - body['tags'] = tags - - return send_request(path, method='post', body=body) +def delete_asset_command(client: Client, args: Dict[str, Any]): + asset_id = args.get('asset_id') - -def delete_asset_command(): - asset_id = demisto.args()['asset_id'] - - res = delete_asset(asset_id) + res = client.delete_asset(asset_id) if not res: return_error('Error: Could not delete the asset') @@ -517,16 +742,10 @@ def delete_asset_command(): }) -def delete_asset(asset_id): - path = 'asset/' + asset_id - - return send_request(path, method='delete') - - -def list_report_definitions_command(): - res = get_report_definitions('id,name,description,modifiedTime,type,ownerGroup,owner') +def list_report_definitions_command(client: Client, args: Dict[str, Any]): + res = client.get_report_definitions('id,name,description,modifiedTime,type,ownerGroup,owner') - manageable = demisto.args().get('manageable', 'false').lower() + manageable = args.get('manageable', 'false').lower() if not res or 'response' not in res or not res['response']: return_message('No report definitions found') @@ -566,20 +785,8 @@ def list_report_definitions_command(): }) -def get_report_definitions(fields): - path = 'reportDefinition' - params = None - - if fields: - params = { - 'fields': fields - } - - return send_request(path, params=params) - - -def list_zones_command(): - res = get_zones() +def list_zones_command(client: Client, args: Dict[str, Any]): + res = client.get_zones() if not res or 'response' not in res: return_message('No zones found') @@ -616,12 +823,6 @@ def list_zones_command(): }) -def get_zones(): - path = 'zone' - - return send_request(path) - - def get_elements(elements, manageable): if manageable == 'false': return elements.get('usable') @@ -629,24 +830,24 @@ def get_elements(elements, manageable): return elements.get('manageable') -def create_scan_command(): - name = demisto.args()['name'] - repo_id = demisto.args()['repository_id'] - policy_id = demisto.args()['policy_id'] - plugin_id = demisto.args().get('plugin_id') - description = demisto.args().get('description') - zone_id = demisto.args().get('zone_id') - schedule = demisto.args().get('schedule') - asset_ids = demisto.args().get('asset_ids') - ips = demisto.args().get('ip_list') - scan_virtual_hosts = demisto.args().get('scan_virtual_hosts') - report_ids = demisto.args().get('report_ids') - credentials = demisto.args().get('credentials') - timeout_action = demisto.args().get('timeout_action') - max_scan_time = demisto.args().get('max_scan_time') - dhcp_track = demisto.args().get('dhcp_tracking') - rollover_type = demisto.args().get('rollover_type') - dependent = demisto.args().get('dependent_id') +def create_scan_command(client: Client, args: Dict[str, Any]): + name = args.get('name') + repo_id = args.get('repository_id') + policy_id = args.get('policy_id') + plugin_id = args.get('plugin_id') + description = args.get('description') + zone_id = args.get('zone_id') + schedule = args.get('schedule') + asset_ids = args.get('asset_ids') + ips = args.get('ip_list') + scan_virtual_hosts = args.get('scan_virtual_hosts') + report_ids = args.get('report_ids') + credentials = args.get('credentials') + timeout_action = args.get('timeout_action') + max_scan_time = args.get('max_scan_time') + dhcp_track = args.get('dhcp_tracking') + rollover_type = args.get('rollover_type') + dependent = args.get('dependent_id') if not asset_ids and not ips: return_error('Error: Assets and/or IPs must be provided') @@ -654,9 +855,9 @@ def create_scan_command(): if schedule == 'dependent' and not dependent: return_error('Error: Dependent schedule must include a dependent scan ID') - res = create_scan(name, repo_id, policy_id, plugin_id, description, zone_id, schedule, asset_ids, - ips, scan_virtual_hosts, report_ids, credentials, timeout_action, max_scan_time, - dhcp_track, rollover_type, dependent) + res = client.create_scan(name, repo_id, policy_id, plugin_id, description, zone_id, schedule, asset_ids, + ips, scan_virtual_hosts, report_ids, credentials, timeout_action, max_scan_time, + dhcp_track, rollover_type, dependent) if not res or 'response' not in res: return_error('Error: Could not retrieve the scan') @@ -695,89 +896,15 @@ def create_scan_command(): }) -def create_scan(name, repo_id, policy_id, plugin_id, description, zone_id, schedule, asset_ids, - ips, scan_virtual_hosts, report_ids, credentials, timeout_action, max_scan_time, - dhcp_track, rollover_type, dependent): - path = 'scan' - - scan_type = 'policy' if policy_id else 'plugin' - - body = { - 'name': name, - 'type': scan_type, - 'repository': { - 'id': repo_id - } - } - - if policy_id: - body['policy'] = { - 'id': policy_id - } - - if plugin_id: - body['pluginID'] = plugin_id - - if description: - body['description'] = description - - if zone_id: - body['zone'] = { - 'id': zone_id - } - - if dhcp_track: - body['dhcpTracking'] = dhcp_track - - if schedule: - body['schedule'] = { - 'type': schedule - } - - if dependent: - body['schedule']['dependentID'] = dependent - - if report_ids: - body['reports'] = [{'id': r_id, 'reportSource': 'individual'} for r_id in argToList(report_ids)] - - if asset_ids: - if str(asset_ids).startswith('All'): - manageable = True if asset_ids == 'AllManageable' else False - res = get_assets(None) - assets = get_elements(res['response'], manageable) - asset_ids = list(map(lambda a: a['id'], assets)) - body['assets'] = [{'id': a_id} for a_id in argToList(asset_ids)] - - if credentials: - body['credentials'] = [{'id': c_id} for c_id in argToList(credentials)] - - if timeout_action: - body['timeoutAction'] = timeout_action - - if scan_virtual_hosts: - body['scanningVirtualHosts'] = scan_virtual_hosts - - if rollover_type: - body['rolloverType'] = rollover_type - - if ips: - body['ipList'] = ips - - if max_scan_time: - body['maxScanTime'] = max_scan_time * 3600 - - return send_request(path, method='post', body=body) - - -def launch_scan_command(): - scan_id = demisto.args()['scan_id'] - target_address = demisto.args().get('diagnostic_target') - target_password = demisto.args().get('diagnostic_password') +def launch_scan_command(client: Client, args: Dict[str, Any]): + scan_id = args.get('scan_id') + target_address = args.get('diagnostic_target') + target_password = args.get('diagnostic_password') if (target_address and not target_password) or (target_password and not target_address): return_error('Error: If a target is provided, both IP/Hostname and the password must be provided') - res = launch_scan(scan_id, {'address': target_address, 'password': target_password}) + res = client.launch_scan(scan_id, {'address': target_address, 'password': target_password}) if not res or 'response' not in res or not res['response'] or 'scanResult' not in res['response']: return_error('Error: Could not retrieve the scan') @@ -812,24 +939,12 @@ def launch_scan_command(): }) -def launch_scan(scan_id, scan_target): - path = 'scan/' + scan_id + '/launch' - body = None - if scan_target: - body = { - 'diagnosticTarget': scan_target['address'], - 'diagnosticPassword': scan_target['password'] - } - - return send_request(path, 'post', body=body) - - -def get_scan_status_command(): - scan_results_ids = argToList(demisto.args()['scan_results_id']) +def get_scan_status_command(client: Client, args: Dict[str, Any]): + scan_results_ids = argToList(args.get('scan_results_id')) scans_results = [] for scan_results_id in scan_results_ids: - res = get_scan_results(scan_results_id) + res = client.get_scan_results(scan_results_id) if not res or 'response' not in res or not res['response']: return_message('Scan results not found') @@ -856,17 +971,11 @@ def get_scan_status_command(): }) -def get_scan_results(scan_results_id): - path = 'scanResult/' + scan_results_id - - return send_request(path) +def get_scan_report_command(client: Client, args: Dict[str, Any]): + scan_results_id = args.get('scan_results_id') + vulnerabilities_to_get = argToList(args.get('vulnerability_severity', [])) - -def get_scan_report_command(): - scan_results_id = demisto.args()['scan_results_id'] - vulnerabilities_to_get = argToList(demisto.args().get('vulnerability_severity', [])) - - res = get_scan_report(scan_results_id) + res = client.get_scan_report(scan_results_id) if not res or 'response' not in res or not res['response']: return_message('Scan results not found') @@ -898,7 +1007,7 @@ def get_scan_report_command(): mapped_results, headers, removeNull=True) if len(vulnerabilities_to_get) > 0: - vulns = get_vulnearbilites(scan_results_id) + vulns = get_vulnerabilities(client, scan_results_id) if isinstance(vulns, list): vulnerabilities = list(filter(lambda v: v['Severity'] in vulnerabilities_to_get, vulns)) @@ -918,23 +1027,12 @@ def get_scan_report_command(): }) -def get_scan_report(scan_results_id): - path = 'scanResult/' + scan_results_id - - params = { - 'fields': 'name,description,details,status,scannedIPs,progress,startTime,scanDuration,importStart,' - 'finishTime,completedChecks,owner,ownerGroup,repository,policy' - } - - return send_request(path, params=params) - - -def list_plugins_command(): - name = demisto.args().get('name'), - cve = demisto.args().get('cve'), - plugin_type = demisto.args().get('type') +def list_plugins_command(client: Client, args: Dict[str, Any]): + name = args.get('name'), + cve = args.get('cve'), + plugin_type = args.get('type') - res = list_plugins(name, plugin_type, cve) + res = client.list_plugins(name, plugin_type, cve) if not res or 'response' not in res: return_message('No plugins found') @@ -962,33 +1060,15 @@ def list_plugins_command(): }) -def list_plugins(name, plugin_type, cve): - path = 'plugin' - - params = { - 'fields': 'id,type,name,description,family' - } - - if cve: - params['filterField'] = 'xrefs:CVE' - params['op'] = 'eq' - params['value'] = cve - - if plugin_type: - params['type'] = plugin_type - - return send_request(path, params=params) - - -def get_vulnearbilites(scan_results_id): - query = create_query(scan_results_id, 'vulnipdetail') +def get_vulnerabilities(client: Client, scan_results_id): + query = client.create_query(scan_results_id, 'vulnipdetail') if not query or 'response' not in query: return 'Could not get vulnerabilites query' - analysis = get_analysis(query['response']['id'], scan_results_id) + analysis = client.get_analysis(query['response']['id'], scan_results_id) - delete_query(query.get('response', {}).get('id')) + client.delete_query(query.get('response', {}).get('id')) if not analysis or 'response' not in analysis: return 'Could not get vulnerabilites analysis' @@ -1025,54 +1105,11 @@ def get_vulnearbilites(scan_results_id): return mapped_vulns -def create_query(scan_id, tool, query_filters=None): - path = 'query' - - body = { - 'name': 'scan ' + scan_id + ' query', - 'type': 'vuln', - 'tool': tool, - 'scanID': scan_id - } - - if query_filters: - body['filters'] = query_filters - - return send_request(path, method='post', body=body) - - -def delete_query(query_id): - if not query_id: - return_error('query id returned None') - path = 'query/' + str(query_id) - send_request(path, method='delete') - - -def get_analysis(query, scan_results_id): - path = 'analysis' - - # This function can receive 'query' argument either as a dict (as in get_vulnerability_command), - # or as an ID of an existing query (as in get_vulnearbilites). - # Here we form the query field in the request body as a dict, as required. - if not isinstance(query, dict): - query = {'id': query} - - body = { - 'type': 'vuln', - 'query': query, - 'sourceType': 'individual', - 'scanID': scan_results_id, - 'view': 'all' - } - - return send_request(path, method='post', body=body) - - -def get_vulnerability_command(): - vuln_id = demisto.args()['vulnerability_id'] - scan_results_id = demisto.args()['scan_results_id'] - page = int(demisto.args().get('page')) - limit = int(demisto.args().get('limit')) +def get_vulnerability_command(client: Client, args: Dict[str, Any]): + vuln_id = args.get('vulnerability_id') + scan_results_id = args.get('scan_results_id') + page = int(args.get('page', '0')) + limit = int(args.get('limit', '50')) if limit > 200: limit = 200 @@ -1091,7 +1128,7 @@ def get_vulnerability_command(): 'endOffset': page + limit # Upper bound for the results list (must be specified) } - analysis = get_analysis(query, scan_results_id) + analysis = client.get_analysis(query, scan_results_id) if not analysis or 'response' not in analysis: return_error('Error: Could not get vulnerability analysis') @@ -1101,7 +1138,7 @@ def get_vulnerability_command(): if not results or len(results) == 0: return_error('Error: Vulnerability not found in the scan results') - vuln_response = get_vulnerability(vuln_id) + vuln_response = client.get_vulnerability(vuln_id) if not vuln_response or 'response' not in vuln_response: return_message('Vulnerability not found') @@ -1194,17 +1231,6 @@ def get_vulnerability_command(): }) -def get_vulnerability(vuln_id): - path = 'plugin/' + vuln_id - - params = { - 'fields': 'name,description,family,type,cpe,riskFactor,solution,synopsis,exploitEase,exploitAvailable,' - 'cvssVector,baseScore,pluginPubDate,pluginModDate,vulnPubDate,temporalScore,xrefs,checkType' - } - - return send_request(path, params=params) - - def get_vulnerability_hosts_from_analysis(results): return [{ 'IP': host['ip'], @@ -1214,67 +1240,10 @@ def get_vulnerability_hosts_from_analysis(results): } for host in results] -def stop_scan_command(): - scan_results_id = demisto.args()['scanResultsID'] - - res = change_scan_status(scan_results_id, 'stop') - - if not res: - return_error('Error: Could not stop the scan') - - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['text'], - 'HumanReadable': 'Scan succsefully stopped' - }) - - -def pause_scan_command(): - scan_results_id = demisto.args()['scanResultsID'] - - res = change_scan_status(scan_results_id, 'pause') - - if not res: - return_error('Error: Could not pause the scan') - - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['text'], - 'HumanReadable': 'Successfully paused the scan' - }) - - -def resume_scan_command(): - scan_results_id = demisto.args()['scanResultsID'] - - res = change_scan_status(scan_results_id, 'resume') - - if not res: - return_error('Error: Could not resume the scan') - - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['text'], - 'HumanReadable': 'Scan successfully resumed' - }) - - -def change_scan_status(scan_results_id, status): - path = 'scanResult/' + scan_results_id + '/' + status - - return send_request(path, method='post') - - -def delete_scan_command(): - scan_id = demisto.args()['scan_id'] +def delete_scan_command(client: Client, args: Dict[str, Any]): + scan_id = args.get('scan_id') - res = delete_scan(scan_id) + res = client.delete_scan(scan_id) if not res: return_error('Error: Could not delete the scan') @@ -1288,19 +1257,13 @@ def delete_scan_command(): }) -def delete_scan(scan_id): - path = 'scan/' + scan_id +def get_device_command(client: Client, args: Dict[str, Any]): + uuid = args.get('uuid') + ip = args.get('ip') + dns_name = args.get('dns_name') + repo = args.get('repository_id') - return send_request(path, method='delete') - - -def get_device_command(): - uuid = demisto.args().get('uuid') - ip = demisto.args().get('ip') - dns_name = demisto.args().get('dns_name') - repo = demisto.args().get('repository_id') - - res = get_device(uuid, ip, dns_name, repo) + res = client.get_device(uuid, ip, dns_name, repo) if not res or 'response' not in res: return_message('Device not found') @@ -1363,29 +1326,12 @@ def get_device_command(): }) -def get_device(uuid, ip, dns_name, repo): - path = 'repository/' + repo + '/' if repo else '' - path += 'deviceInfo' - params = { - 'fields': 'ip,uuid,macAddress,netbiosName,dnsName,os,osCPE,lastScan,repository,total,severityLow,' - 'severityMedium,severityHigh,severityCritical' - } - if uuid: - params['uuid'] = uuid - else: - params['ip'] = ip - if dns_name: - params['dnsName'] = dns_name - - return send_request(path, params=params) - - -def list_users_command(): - user_id = demisto.args().get('id') - username = demisto.args().get('username') - email = demisto.args().get('email') +def list_users_command(client: Client, args: Dict[str, Any]): + user_id = args.get('id') + username = args.get('username') + email = args.get('email') - res = get_users('id,username,firstname,lastname,title,email,createdTime,modifiedTime,lastLogin,role', user_id) + res = client.get_users('id,username,firstname,lastname,title,email,createdTime,modifiedTime,lastLogin,role', user_id) if not res or 'response' not in res: return_message('No users found') @@ -1442,24 +1388,8 @@ def list_users_command(): }) -def get_users(fields, user_id): - path = 'user' - - if user_id: - path += '/' + user_id - - params = None - - if fields: - params = { - 'fields': fields - } - - return send_request(path, params=params) - - -def get_system_licensing_command(): - res = get_system_licensing() +def get_system_licensing_command(client: Client, args: Dict[str, Any]): + res = client.get_system_licensing() if not res or 'response' not in res: return_error('Error: Could not retrieve system licensing') @@ -1491,19 +1421,13 @@ def get_system_licensing_command(): }) -def get_system_licensing(): - path = 'status' - - return send_request(path) - - -def get_system_information_command(): - sys_res = get_system() +def get_system_information_command(client: Client, args: Dict[str, Any]): + sys_res = client.get_system() if not sys_res or 'response' not in sys_res: return_error('Error: Could not retrieve system information') - diag_res = get_system_diagnostics() + diag_res = client.get_system_diagnostics() if not diag_res or 'response' not in diag_res: return_error('Error: Could not retrieve system information') @@ -1549,22 +1473,10 @@ def get_system_information_command(): }) -def get_system_diagnostics(): - path = 'system/diagnostics' - - return send_request(path) - - -def get_system(): - path = 'system' - - return send_request(path) - - -def list_alerts_command(): - res = get_alerts(fields='id,name,description,didTriggerLastEvaluation,lastTriggered,' +def list_alerts_command(client: Client, args: Dict[str, Any]): + res = client.get_alerts(fields='id,name,description,didTriggerLastEvaluation,lastTriggered,' 'action,lastEvaluated,ownerGroup,owner') - manageable = demisto.args().get('manageable', 'false').lower() + manageable = args.get('manageable', 'false').lower() if not res or 'response' not in res or not res['response']: return_message('No alerts found') @@ -1598,15 +1510,15 @@ def list_alerts_command(): }) -def get_alert_command(): - alert_id = demisto.args()['alert_id'] - res = get_alerts(alert_id=alert_id) +def get_alert_command(client: Client, args: Dict[str, Any]): + alert_id = args.get('alert_id') + res = client.get_alerts(alert_id=alert_id) if not res or 'response' not in res or not res['response']: return_message('Alert not found') alert = res['response'] - query_res = get_query(alert['query'].get('id')) + query_res = client.get_query(alert['query'].get('id')) query = query_res.get('response') alert_headers = ['ID', 'Name', 'Description', 'LastTriggered', 'State', 'Behavior', 'Actions'] @@ -1664,41 +1576,20 @@ def get_alert_command(): }) -def get_alerts(fields=None, alert_id=None): - path = 'alert' - params = {} # type: Dict[str, Any] - - if alert_id: - path += '/' + alert_id - - if fields: - params = { - 'fields': fields - } - - return send_request(path, params=params) - - -def get_query(query_id): - path = 'query/' + query_id - - return send_request(path) - - -def fetch_incidents(): +def fetch_incidents(client: Client, first_fetch: str = '3 days'): incidents = [] last_run = demisto.getLastRun() if not last_run: last_run = {} if 'time' not in last_run: # get timestamp in seconds - timestamp, _ = parse_date_range(FETCH_TIME, to_timestamp=True) + timestamp, _ = parse_date_range(first_fetch, to_timestamp=True) timestamp /= 1000 else: timestamp = last_run['time'] max_timestamp = timestamp - res = get_alerts( + res = client.get_alerts( fields='id,name,description,lastTriggered,triggerName,triggerOperator,' 'triggerValue,action,query,owner,ownerGroup,schedule,canManage') @@ -1719,20 +1610,11 @@ def fetch_incidents(): demisto.setLastRun({'time': max_timestamp}) -def get_all_scan_results(): - path = 'scanResult' - params = { - 'fields': 'name,description,details,status,scannedIPs,startTime,scanDuration,importStart,' - 'finishTime,completedChecks,owner,ownerGroup,repository' - } - return send_request(path, params=params) - - -def get_all_scan_results_command(): - res = get_all_scan_results() - get_manageable_results = demisto.args().get('manageable', 'false').lower() # 'true' or 'false' - page = int(demisto.args().get('page')) - limit = int(demisto.args().get('limit')) +def get_all_scan_results_command(client: Client, args: Dict[str, Any]): + res = client.get_all_scan_results() + get_manageable_results = args.get('manageable', 'false').lower() # 'true' or 'false' + page = int(args.get('page', '0')) + limit = int(args.get('limit', '50')) if limit > 200: limit = 200 @@ -1790,70 +1672,75 @@ def scan_duration_to_demisto_format(duration, default_returned_value=''): return default_returned_value -''' LOGIC ''' - -LOG('Executing command ' + demisto.command()) - - -try: - if not TOKEN or not COOKIE: - login() - - if demisto.command() == 'test-module': - demisto.results('ok') - elif demisto.command() == 'fetch-incidents': - fetch_incidents() - elif demisto.command() == 'tenable-sc-list-scans': - list_scans_command() - elif demisto.command() == 'tenable-sc-list-policies': - list_policies_command() - elif demisto.command() == 'tenable-sc-list-repositories': - list_repositories_command() - elif demisto.command() == 'tenable-sc-list-credentials': - list_credentials_command() - elif demisto.command() == 'tenable-sc-list-zones': - list_zones_command() - elif demisto.command() == 'tenable-sc-list-report-definitions': - list_report_definitions_command() - elif demisto.command() == 'tenable-sc-list-assets': - list_assets_command() - elif demisto.command() == 'tenable-sc-list-plugins': - list_plugins_command() - elif demisto.command() == 'tenable-sc-get-asset': - get_asset_command() - elif demisto.command() == 'tenable-sc-create-asset': - create_asset_command() - elif demisto.command() == 'tenable-sc-delete-asset': - delete_asset_command() - elif demisto.command() == 'tenable-sc-create-scan': - create_scan_command() - elif demisto.command() == 'tenable-sc-launch-scan': - launch_scan_command() - elif demisto.command() == 'tenable-sc-get-scan-status': - get_scan_status_command() - elif demisto.command() == 'tenable-sc-get-scan-report': - get_scan_report_command() - elif demisto.command() == 'tenable-sc-get-vulnerability': - get_vulnerability_command() - elif demisto.command() == 'tenable-sc-delete-scan': - delete_scan_command() - elif demisto.command() == 'tenable-sc-get-device': - get_device_command() - elif demisto.command() == 'tenable-sc-list-users': - list_users_command() - elif demisto.command() == 'tenable-sc-list-alerts': - list_alerts_command() - elif demisto.command() == 'tenable-sc-get-alert': - get_alert_command() - elif demisto.command() == 'tenable-sc-get-system-information': - get_system_information_command() - elif demisto.command() == 'tenable-sc-get-system-licensing': - get_system_licensing_command() - elif demisto.command() == 'tenable-sc-get-all-scan-results': - get_all_scan_results_command() -except Exception as e: - LOG(e) - LOG.print_log(False) - return_error(str(e)) -finally: - logout() +def main(): + params = demisto.params() + command = demisto.command() + args = demisto.args() + verify_ssl = not params.get('unsecure', False) + proxy = params.get('proxy', False) + user_name = params.get('credentials', {}).get('identifier') + password = params.get('credentials', {}).get('password') + access_key = params.get('creds_keys', {}).get('identifier') + secret_key = params.get('creds_keys', {}).get('password') + url = params.get('server') + + client = Client( + verify_ssl=verify_ssl, + proxy=proxy, + user_name=user_name, + password=password, + access_key=access_key, + secret_key=secret_key, + url=url + ) + + demisto.info(f'Executing command {command}') + + command_dict = { + 'tenable-sc-list-scans': list_scans_command, + 'tenable-sc-list-policies': list_policies_command, + 'tenable-sc-list-repositories': list_repositories_command, + 'tenable-sc-list-credentials': list_credentials_command, + 'tenable-sc-list-zones': list_zones_command, + 'tenable-sc-list-report-definitions': list_report_definitions_command, + 'tenable-sc-list-assets': list_assets_command, + 'tenable-sc-list-plugins': list_plugins_command, + 'tenable-sc-get-asset': get_asset_command, + 'tenable-sc-create-asset': create_asset_command, + 'tenable-sc-delete-asset': delete_asset_command, + 'tenable-sc-create-scan': create_scan_command, + 'tenable-sc-launch-scan': launch_scan_command, + 'tenable-sc-get-scan-status': get_scan_status_command, + 'tenable-sc-get-scan-report': get_scan_report_command, + 'tenable-sc-get-vulnerability': get_vulnerability_command, + 'tenable-sc-delete-scan': delete_scan_command, + 'tenable-sc-get-device': get_device_command, + 'tenable-sc-list-users': list_users_command, + 'tenable-sc-list-alerts': list_alerts_command, + 'tenable-sc-get-alert': get_alert_command, + 'tenable-sc-get-system-information': get_system_information_command, + 'tenable-sc-get-system-licensing': get_system_licensing_command, + 'tenable-sc-get-all-scan-results': get_all_scan_results_command + } + + try: + if not client.token or not client.cookie: + client.login() + + if command == 'test-module': + demisto.results('ok') + elif command == 'fetch-incidents': + first_fetch = params.get('fetch_time').strip() + fetch_incidents(client, first_fetch) + else: + command_dict[command](client, args) + except Exception as e: + return_error( + f'Failed to execute {command} command. Error: {str(e)}' + ) + finally: + client.logout() + + +if __name__ in ('__main__', '__builtin__', 'builtins'): + main() diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index 207ac34b846f..bb4fee4366da 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -11,6 +11,12 @@ configuration: required: true type: 0 section: Connect +- display: access key + name: creds_keys + required: false + type: 9 + displaypassword: secret key + section: Connect - display: Username name: credentials required: true From 4588ac06519cf4438e3dbbfd78633d7f88c5d23b Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Wed, 3 May 2023 12:10:15 +0300 Subject: [PATCH 002/115] in progress --- .../Integrations/Tenable_sc/Tenable_sc.py | 84 +++++++++++++------ .../Integrations/Tenable_sc/Tenable_sc.yml | 3 +- 2 files changed, 61 insertions(+), 26 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 6ce8759b0f6e..4d0a421929a7 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -31,25 +31,62 @@ def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str self.max_retries = 3 self.session = Session() self.url = get_server_url(url) + '/rest' - self.access_key = access_key - self.secret_key = secret_key - self.user_name = user_name - self.password = password - - self.headers = { + self.headers: dict[str, Any] = { 'Accept': 'application/json', 'Content-Type': 'application/json' } + integration_context = demisto.getIntegrationContext() + self.cookie = integration_context.get('cookie') + if not (user_name and password) and not (secret_key and access_key): + return_error("Please provide either user_name and password or secret_key and access_key") + if secret_key and access_key: + self.headers['x-apikey'] = {'accesskey': access_key, 'secretkey': secret_key} + self.send_request = self.send_request_new + else: + self.token = integration_context.get('token') + self.user_name = user_name + self.password = password + self.send_request = self.send_request_old + if not self.token or not self.cookie: + self.login() + if not proxy: del os.environ['HTTP_PROXY'] del os.environ['HTTPS_PROXY'] del os.environ['http_proxy'] del os.environ['https_proxy'] - integration_context = demisto.getIntegrationContext() - self.token = integration_context.get('token') - self.cookie = integration_context.get('cookie') - def send_request(self, path, method='get', body=None, params=None, headers=None, try_number=1): + def send_request_new(self, path, method='get', body=None, params=None, headers=None, try_number=1): + body = body if body is not None else {} + params = params if params is not None else {} + headers = headers if headers is not None else self.headers + + url = f'{self.url}/{path}' + + session_cookie = cookies.create_cookie('TNS_SESSIONID', self.cookie) + self.session.cookies.set_cookie(session_cookie) # type: ignore + + res = self.session.request(method, url, data=json.dumps(body), params=params, headers=headers, verify=self.verify_ssl) + + if res.status_code == 403 and try_number <= self.max_retries: + self.login() + headers['X-SecurityCenter'] = self.token # The Token is being updated in the login + return self.send_request(path, method, body, params, headers, try_number + 1) + + elif res.status_code < 200 or res.status_code >= 300: + try: + error = res.json() + except Exception: + # type: ignore + return_error( + f'Error: Got status code {str(res.status_code)} with {url=} \ + with body {res.content} with headers {str(res.headers)}') # type: ignore + + return_error(f"Error: Got an error from TenableSC, code: {error['error_code']}, \ + details: {error['error_msg']}") # type: ignore + return res.json() + + def send_request_old(self, path, method='get', body=None, params=None, headers=None, try_number=1): body = body if body is not None else {} params = params if params is not None else {} headers = headers if headers is not None else self.headers @@ -104,8 +141,7 @@ def login(self): demisto.setIntegrationContext({'token': token}) def send_login_request(self, login_body): - path = 'token' - url = '{}/{}'.format(self.url, path) + url = f'{self.url}/token' headers = self.headers res = self.session.request('post', url, headers=headers, data=json.dumps(login_body), verify=self.verify_ssl) @@ -1684,16 +1720,6 @@ def main(): secret_key = params.get('creds_keys', {}).get('password') url = params.get('server') - client = Client( - verify_ssl=verify_ssl, - proxy=proxy, - user_name=user_name, - password=password, - access_key=access_key, - secret_key=secret_key, - url=url - ) - demisto.info(f'Executing command {command}') command_dict = { @@ -1724,8 +1750,15 @@ def main(): } try: - if not client.token or not client.cookie: - client.login() + client = Client( + verify_ssl=verify_ssl, + proxy=proxy, + user_name=user_name, + password=password, + access_key=access_key, + secret_key=secret_key, + url=url + ) if command == 'test-module': demisto.results('ok') @@ -1739,7 +1772,8 @@ def main(): f'Failed to execute {command} command. Error: {str(e)}' ) finally: - client.logout() + if (user_name and password) and not (access_key and secret_key): + client.logout() if __name__ in ('__main__', '__builtin__', 'builtins'): diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index bb4fee4366da..b6938ba53928 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -19,7 +19,8 @@ configuration: section: Connect - display: Username name: credentials - required: true + required: false + hidden: true type: 9 section: Connect - display: Trust any certificate (not secure) From 5a49f9a5bd1544c242ea58402d7560ecbf8613b4 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Wed, 3 May 2023 13:48:37 +0300 Subject: [PATCH 003/115] in progress --- .../Integrations/Tenable_sc/Tenable_sc.py | 43 ++++--------------- 1 file changed, 9 insertions(+), 34 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 4d0a421929a7..42b2624dc014 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -26,24 +26,25 @@ class Client: def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str = "", - password: str = "", access_key: str = "", secret_key: str = "", url: str = "",): + password: str = "", access_key: str = "", secret_key: str = "", url: str = ""): + self.url = f"{get_server_url(url)}/rest" self.verify_ssl = verify_ssl self.max_retries = 3 - self.session = Session() - self.url = get_server_url(url) + '/rest' self.headers: dict[str, Any] = { 'Accept': 'application/json', 'Content-Type': 'application/json' } - integration_context = demisto.getIntegrationContext() - self.cookie = integration_context.get('cookie') if not (user_name and password) and not (secret_key and access_key): return_error("Please provide either user_name and password or secret_key and access_key") if secret_key and access_key: self.headers['x-apikey'] = {'accesskey': access_key, 'secretkey': secret_key} + BaseClient.__init__(self, base_url=self.url, headers=self.headers, verify=verify_ssl, proxy=proxy) self.send_request = self.send_request_new else: + self.session = Session() + integration_context = demisto.getIntegrationContext() self.token = integration_context.get('token') + self.cookie = integration_context.get('cookie') self.user_name = user_name self.password = password self.send_request = self.send_request_old @@ -56,35 +57,9 @@ def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str del os.environ['http_proxy'] del os.environ['https_proxy'] - def send_request_new(self, path, method='get', body=None, params=None, headers=None, try_number=1): - body = body if body is not None else {} - params = params if params is not None else {} - headers = headers if headers is not None else self.headers - - url = f'{self.url}/{path}' - - session_cookie = cookies.create_cookie('TNS_SESSIONID', self.cookie) - self.session.cookies.set_cookie(session_cookie) # type: ignore - - res = self.session.request(method, url, data=json.dumps(body), params=params, headers=headers, verify=self.verify_ssl) - - if res.status_code == 403 and try_number <= self.max_retries: - self.login() - headers['X-SecurityCenter'] = self.token # The Token is being updated in the login - return self.send_request(path, method, body, params, headers, try_number + 1) - - elif res.status_code < 200 or res.status_code >= 300: - try: - error = res.json() - except Exception: - # type: ignore - return_error( - f'Error: Got status code {str(res.status_code)} with {url=} \ - with body {res.content} with headers {str(res.headers)}') # type: ignore - - return_error(f"Error: Got an error from TenableSC, code: {error['error_code']}, \ - details: {error['error_msg']}") # type: ignore - return res.json() + def send_request_new(self, path, method='get', body=None, params=None, headers=None): + headers = headers or self.headers + return self._http_request(method, url_suffix=path, params=params, body=body) def send_request_old(self, path, method='get', body=None, params=None, headers=None, try_number=1): body = body if body is not None else {} From b9ba674813074ada722c18ab9aae7b6647b0a477 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Wed, 3 May 2023 14:52:00 +0300 Subject: [PATCH 004/115] in progress --- .../Integrations/Tenable_sc/Tenable_sc.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 42b2624dc014..0339a73351c1 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -24,9 +24,16 @@ } -class Client: +class Client(BaseClient): def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str = "", password: str = "", access_key: str = "", secret_key: str = "", url: str = ""): + + if not proxy: + del os.environ['HTTP_PROXY'] + del os.environ['HTTPS_PROXY'] + del os.environ['http_proxy'] + del os.environ['https_proxy'] + self.url = f"{get_server_url(url)}/rest" self.verify_ssl = verify_ssl self.max_retries = 3 @@ -51,15 +58,9 @@ def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str if not self.token or not self.cookie: self.login() - if not proxy: - del os.environ['HTTP_PROXY'] - del os.environ['HTTPS_PROXY'] - del os.environ['http_proxy'] - del os.environ['https_proxy'] - def send_request_new(self, path, method='get', body=None, params=None, headers=None): headers = headers or self.headers - return self._http_request(method, url_suffix=path, params=params, body=body) + return self._http_request(method, url_suffix=path, params=params, data=body) def send_request_old(self, path, method='get', body=None, params=None, headers=None, try_number=1): body = body if body is not None else {} From 8ed6135159cc6a7b92ad2c74e2a52938ee4c7182 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Thu, 4 May 2023 12:52:08 +0300 Subject: [PATCH 005/115] in progress --- .../Integrations/Tenable_sc/Tenable_sc.py | 31 +++++-- .../Integrations/Tenable_sc/Tenable_sc.yml | 90 +++++++++++++++---- 2 files changed, 98 insertions(+), 23 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 0339a73351c1..e875ed0176f0 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -7,6 +7,7 @@ import json from datetime import datetime from requests import cookies +import pytz from typing import Dict, Any # disable insecure warnings @@ -44,7 +45,7 @@ def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str if not (user_name and password) and not (secret_key and access_key): return_error("Please provide either user_name and password or secret_key and access_key") if secret_key and access_key: - self.headers['x-apikey'] = {'accesskey': access_key, 'secretkey': secret_key} + self.headers['x-apikey'] = f"accesskey={access_key}; secretkey={secret_key}" BaseClient.__init__(self, base_url=self.url, headers=self.headers, verify=verify_ssl, proxy=proxy) self.send_request = self.send_request_new else: @@ -136,8 +137,8 @@ def logout(self): def create_scan(self, name, repo_id, policy_id, plugin_id, description, zone_id, schedule, asset_ids, ips, scan_virtual_hosts, report_ids, credentials, timeout_action, max_scan_time, - dhcp_track, rollover_type, dependent): - path = 'scan' + dhcp_track, rollover_type, dependent, start_time, repeat_rule_freq, repeat_rule_interval, + repeat_rule_by_day, enabled, time_zone): scan_type = 'policy' if policy_id else 'plugin' @@ -176,6 +177,14 @@ def create_scan(self, name, repo_id, policy_id, plugin_id, description, zone_id, if dependent: body['schedule']['dependentID'] = dependent + if schedule == 'ical': + if time_zone and start_time: + body['schedule']['start'] = f"TZID={time_zone}:{start_time}" + else: + return_error("Please make sure to provide both time_zone and start_time") + body['schedule']['repeatRule'] = dependent + + if report_ids: body['reports'] = [{'id': r_id, 'reportSource': 'individual'} for r_id in argToList(report_ids)] @@ -205,7 +214,7 @@ def create_scan(self, name, repo_id, policy_id, plugin_id, description, zone_id, if max_scan_time: body['maxScanTime'] = max_scan_time * 3600 - return self.send_request(path, method='post', body=body) + return self.send_request(path='scan', method='post', body=body) def get_scan_results(self, scan_results_id): path = 'scanResult/' + scan_results_id @@ -860,7 +869,16 @@ def create_scan_command(client: Client, args: Dict[str, Any]): dhcp_track = args.get('dhcp_tracking') rollover_type = args.get('rollover_type') dependent = args.get('dependent_id') - + time_zone = args.get("time_zone") + start_time = args.get("start_time") + repeat_rule_freq = args.get("repeat_rule_freq") + repeat_rule_interval = int(args.get("repeat_rule_interval", 0)) + repeat_rule_by_day = argToList(args.get("repeat_rule_by_day"), []) + enabled = argToBoolean(args.get("enabled", True)) + + if time_zone and time_zone not in pytz.all_timezones: + return_error("Invalid time zone ID. Please choose one of the following: " + "https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html") if not asset_ids and not ips: return_error('Error: Assets and/or IPs must be provided') @@ -869,7 +887,8 @@ def create_scan_command(client: Client, args: Dict[str, Any]): res = client.create_scan(name, repo_id, policy_id, plugin_id, description, zone_id, schedule, asset_ids, ips, scan_virtual_hosts, report_ids, credentials, timeout_action, max_scan_time, - dhcp_track, rollover_type, dependent) + dhcp_track, rollover_type, dependent, start_time, repeat_rule_freq, repeat_rule_interval, + repeat_rule_by_day, enabled, time_zone) if not res or 'response' not in res: return_error('Error: Could not retrieve the scan') diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index b6938ba53928..57e8d81f50cb 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -18,9 +18,10 @@ configuration: displaypassword: secret key section: Connect - display: Username + hiddenusername: true + hiddenpassword: true name: credentials required: false - hidden: true type: 9 section: Connect - display: Trust any certificate (not secure) @@ -484,38 +485,44 @@ script: type: number - arguments: - default: false - description: Scan name + description: Scan name. isArray: false name: name required: true secret: false - default: false - description: Policy ID, can be retrieved from list-policies command + description: Policy ID, can be retrieved from list-policies command. isArray: false name: policy_id required: true secret: false - default: false - description: Scan description + description: Plugin ID. + isArray: false + name: plugin_id + required: true + secret: false + - default: false + description: Scan description. isArray: false name: description required: false secret: false - default: false - description: Scan Repository ID, can be retrieved from list-repositories command + description: Scan Repository ID, can be retrieved from list-repositories command. isArray: false name: repository_id required: true secret: false - default: false - description: Scan zone ID (default is all zones), can be retrieved from list-zones command + description: Scan zone ID (default is all zones), can be retrieved from list-zones command. isArray: false name: zone_id required: false secret: false - auto: PREDEFINED default: false - description: Schedule for the scan + description: Schedule for the scan. isArray: false name: schedule predefined: @@ -523,11 +530,12 @@ script: - ical - never - rollover + - now required: false secret: false - auto: PREDEFINED default: false - description: Either all assets or comma separated asset IDs to scan, can be retrieved from list-assets command + description: Either all assets or comma separated asset IDs to scan, can be retrieved from list-assets command. isArray: true name: asset_ids predefined: @@ -537,7 +545,7 @@ script: secret: false - auto: PREDEFINED default: false - description: Whether to includes virtual hosts, default false + description: Whether to includes virtual hosts, default false. isArray: false name: scan_virtual_hosts predefined: @@ -546,26 +554,26 @@ script: required: false secret: false - default: false - description: Comma separated IPs to scan e.g 10.0.0.1,10.0.0.2 + description: Comma separated IPs to scan e.g 10.0.0.1,10.0.0.2 . isArray: false name: ip_list required: false secret: false - default: false - description: Comma separated list of report definition IDs to create post-scan, can be retrieved from list-report-definitions command + description: Comma separated list of report definition IDs to create post-scan, can be retrieved from list-report-definitions command. isArray: true name: report_ids required: false secret: false - default: false - description: Comma separated credentials IDs to use, can be retrieved from list-credentials command + description: Comma separated credentials IDs to use, can be retrieved from list-credentials command. isArray: true name: credentials required: false secret: false - auto: PREDEFINED default: false - description: Scan timeout action, default is import + description: Scan timeout action, default is import. isArray: false name: timeout_action predefined: @@ -575,14 +583,14 @@ script: required: false secret: false - default: false - description: Maximum scan run time in hours, default is 1 + description: Maximum scan run time in hours, default is 1. isArray: false name: max_scan_time required: false secret: false - auto: PREDEFINED default: false - description: Track hosts which have been issued new IP address, (e.g. DHCP) + description: Track hosts which have been issued new IP address, (e.g. DHCP). isArray: false name: dhcp_tracking predefined: @@ -592,7 +600,7 @@ script: secret: false - auto: PREDEFINED default: false - description: Scan rollover type + description: Scan rollover type. isArray: false name: rollover_type predefined: @@ -600,11 +608,59 @@ script: required: false secret: false - default: false - description: Dependent scan ID in case of a dependent schedule, can be retrieved from list-scans command + description: Dependent scan ID in case of a dependent schedule, can be retrieved from list-scans command. isArray: false name: dependent_id required: false secret: false + - default: false + description: 'The timezone for the given start_time, possible values can be found here: https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html..' + isArray: false + name: time_zone + required: false + secret: false + - default: false + description: The scan start time, should be in the format of YYYY-MM-DD:HH:MM:SS or relative timestamp (i.e now, 3 days). + isArray: false + name: start_time + required: false + secret: false + - default: false + description: to specify repeating events based on an interval of a repeat_rule_freq or more. + isArray: false + name: repeat_rule_freq + required: false + secret: false + auto: PREDEFINED + predefined: + - HOURLY + - DAILY + - WEEKLY + - MONTHLY + - YEARLY + - default: false + description: 'the number of repeat_rule_freq between each interval (for example: If repeat_rule_freq=DAILY and repeat_rule_interval=8 it means every eight days.)' + isArray: false + name: repeat_rule_interval + required: false + secret: false + - default: false + description: 'A comma-separated list of days of the week to run the schedule at. Possible values are: SU, MO, TU, WE, TH, FR, SA.' + isArray: yes + name: repeat_rule_interval + required: false + secret: false + - auto: PREDEFINED + default: false + description: The "enabled" field can only be set to "false" for schedules of type "ical". For all other schedules types, "enabled" is set to "true". + isArray: false + name: enabled + predefined: + - 'true' + - 'false' + required: false + secret: false + defaultValue: 'true' deprecated: false description: Create a scan on Tenable.sc execution: false From c917229af6196354537338f919b2a0bf053b6888 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Thu, 4 May 2023 15:49:57 +0300 Subject: [PATCH 006/115] in progress --- .../Integrations/Tenable_sc/Tenable_sc.py | 14 +++++++++----- .../Integrations/Tenable_sc/Tenable_sc.yml | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index e875ed0176f0..341ecbc760d5 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -181,9 +181,13 @@ def create_scan(self, name, repo_id, policy_id, plugin_id, description, zone_id, if time_zone and start_time: body['schedule']['start'] = f"TZID={time_zone}:{start_time}" else: - return_error("Please make sure to provide both time_zone and start_time") + return_error("Please make sure to provide both time_zone and start_time.") + if not repeat_rule_freq or not repeat_rule_interval or not repeat_rule_by_day: + return_error("Please make sure to provide both repeat_rule_freq,repeat_rule_interval, and repeat_rule_by_day.") + else: + body['schedule']['FREQ'] = f"{repeat_rule_freq};INTERVAL={repeat_rule_interval};BYDAY={repeat_rule_by_day}" + body['schedule']['enabled'] = enabled body['schedule']['repeatRule'] = dependent - if report_ids: body['reports'] = [{'id': r_id, 'reportSource': 'individual'} for r_id in argToList(report_ids)] @@ -538,9 +542,9 @@ def list_policies_command(client: Client, args: Dict[str, Any]): 'Name': p['name'], 'Description': p['description'], 'Tag': p['tags'], - 'Type': p['policyTemplate'].get('name'), - 'Group': p['ownerGroup'].get('name'), - 'Owner': p['owner'].get('username'), + 'Type': p.get('policyTemplate', {}).get('name'), + 'Group': p.get('ownerGroup', {}).get('name'), + 'Owner': p.get('owner', {}).get('username'), 'LastModified': timestamp_to_utc(p['modifiedTime']) } for p in policies] diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index 57e8d81f50cb..4944fa2e3c3d 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -500,7 +500,7 @@ script: description: Plugin ID. isArray: false name: plugin_id - required: true + required: false secret: false - default: false description: Scan description. From 1c94da7a06c2d0674b83b3901b88a215a05d83c5 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Sun, 7 May 2023 12:21:47 +0300 Subject: [PATCH 007/115] in progress --- .../Integrations/Tenable_sc/Tenable_sc.py | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 341ecbc760d5..2dacef75d394 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -61,7 +61,7 @@ def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str def send_request_new(self, path, method='get', body=None, params=None, headers=None): headers = headers or self.headers - return self._http_request(method, url_suffix=path, params=params, data=body) + return self._http_request(method, url_suffix=path, params=params, data=json.dumps(body)) def send_request_old(self, path, method='get', body=None, params=None, headers=None, try_number=1): body = body if body is not None else {} @@ -137,8 +137,8 @@ def logout(self): def create_scan(self, name, repo_id, policy_id, plugin_id, description, zone_id, schedule, asset_ids, ips, scan_virtual_hosts, report_ids, credentials, timeout_action, max_scan_time, - dhcp_track, rollover_type, dependent, start_time, repeat_rule_freq, repeat_rule_interval, - repeat_rule_by_day, enabled, time_zone): + dhcp_track, rollover_type, dependent, start_time="", repeat_rule_freq="", repeat_rule_interval="", + repeat_rule_by_day="", enabled=True, time_zone=""): scan_type = 'policy' if policy_id else 'plugin' @@ -180,14 +180,13 @@ def create_scan(self, name, repo_id, policy_id, plugin_id, description, zone_id, if schedule == 'ical': if time_zone and start_time: body['schedule']['start'] = f"TZID={time_zone}:{start_time}" - else: + elif (time_zone and not start_time) or (start_time and not time_zone): return_error("Please make sure to provide both time_zone and start_time.") - if not repeat_rule_freq or not repeat_rule_interval or not repeat_rule_by_day: - return_error("Please make sure to provide both repeat_rule_freq,repeat_rule_interval, and repeat_rule_by_day.") - else: - body['schedule']['FREQ'] = f"{repeat_rule_freq};INTERVAL={repeat_rule_interval};BYDAY={repeat_rule_by_day}" + # if not repeat_rule_freq or not repeat_rule_interval or not repeat_rule_by_day: + # return_error("Please make sure to provide both repeat_rule_freq,repeat_rule_interval, and repeat_rule_by_day.") + # else: + # body['schedule']['repeatRule'] = f"FREQ={repeat_rule_freq};INTERVAL={repeat_rule_interval};BYDAY={repeat_rule_by_day}" body['schedule']['enabled'] = enabled - body['schedule']['repeatRule'] = dependent if report_ids: body['reports'] = [{'id': r_id, 'reportSource': 'individual'} for r_id in argToList(report_ids)] @@ -875,9 +874,9 @@ def create_scan_command(client: Client, args: Dict[str, Any]): dependent = args.get('dependent_id') time_zone = args.get("time_zone") start_time = args.get("start_time") - repeat_rule_freq = args.get("repeat_rule_freq") + repeat_rule_freq = args.get("repeat_rule_freq", "") repeat_rule_interval = int(args.get("repeat_rule_interval", 0)) - repeat_rule_by_day = argToList(args.get("repeat_rule_by_day"), []) + repeat_rule_by_day = argToList(args.get("repeat_rule_by_day"), "") enabled = argToBoolean(args.get("enabled", True)) if time_zone and time_zone not in pytz.all_timezones: From 7d4da8e21d5e7d4830b3f38c1229510765a9d759 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Sun, 7 May 2023 14:06:04 +0300 Subject: [PATCH 008/115] in progress --- Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 2dacef75d394..2a3dc63ef3dd 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -178,9 +178,14 @@ def create_scan(self, name, repo_id, policy_id, plugin_id, description, zone_id, body['schedule']['dependentID'] = dependent if schedule == 'ical': + timestamp_format = "%Y-%m-%dT%H:%M:%S" + try: + start_time = datetime.datetime(start_time).strftime(timestamp_format) + except Exception: + start_time = parse_date_range(start_time, date_format=timestamp_format) if time_zone and start_time: body['schedule']['start'] = f"TZID={time_zone}:{start_time}" - elif (time_zone and not start_time) or (start_time and not time_zone): + else: return_error("Please make sure to provide both time_zone and start_time.") # if not repeat_rule_freq or not repeat_rule_interval or not repeat_rule_by_day: # return_error("Please make sure to provide both repeat_rule_freq,repeat_rule_interval, and repeat_rule_by_day.") From c2ac9743885462ffead6a1d20462325b773244b9 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Mon, 8 May 2023 10:23:40 +0300 Subject: [PATCH 009/115] in progress --- .../Integrations/Tenable_sc/Tenable_sc.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 2a3dc63ef3dd..0a3c383e08fb 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -178,19 +178,24 @@ def create_scan(self, name, repo_id, policy_id, plugin_id, description, zone_id, body['schedule']['dependentID'] = dependent if schedule == 'ical': - timestamp_format = "%Y-%m-%dT%H:%M:%S" + timestamp_format = "%Y%m%dT%H%M%S" + expected_format = "%Y-%m-%d:%H:%M:%S" try: - start_time = datetime.datetime(start_time).strftime(timestamp_format) + start_time = datetime.strptime(start_time, expected_format) + start_time = datetime.strftime(start_time, timestamp_format) except Exception: - start_time = parse_date_range(start_time, date_format=timestamp_format) + start_time = parse_date_range(start_time, date_format=timestamp_format)[0] if time_zone and start_time: body['schedule']['start'] = f"TZID={time_zone}:{start_time}" else: return_error("Please make sure to provide both time_zone and start_time.") - # if not repeat_rule_freq or not repeat_rule_interval or not repeat_rule_by_day: - # return_error("Please make sure to provide both repeat_rule_freq,repeat_rule_interval, and repeat_rule_by_day.") - # else: - # body['schedule']['repeatRule'] = f"FREQ={repeat_rule_freq};INTERVAL={repeat_rule_interval};BYDAY={repeat_rule_by_day}" + if all(repeat_rule_freq, repeat_rule_interval, repeat_rule_by_day): + body['schedule']['repeatRule'] = f"FREQ={repeat_rule_freq};INTERVAL={repeat_rule_interval};BYDAY={repeat_rule_by_day}" + elif repeat_rule_freq and repeat_rule_interval: + body['schedule']['repeatRule'] = f"FREQ={repeat_rule_freq};INTERVAL={repeat_rule_interval}" + elif any(repeat_rule_freq, repeat_rule_interval, repeat_rule_by_day): + return_error("Please make sure to provide repeat_rule_freq, repeat_rule_interval with or without " + "repeat_rule_by_day, or don't provide any of them.") body['schedule']['enabled'] = enabled if report_ids: From e69024f1493aa176563b1639af7733034c38548e Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Mon, 8 May 2023 17:21:22 +0300 Subject: [PATCH 010/115] fix --- .../Integrations/Tenable_sc/Tenable_sc.py | 18 ++++++++- .../Integrations/Tenable_sc/Tenable_sc.yml | 38 ++++++++++++++++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 0a3c383e08fb..a4a661fd80d1 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -1152,6 +1152,10 @@ def get_vulnerabilities(client: Client, scan_results_id): def get_vulnerability_command(client: Client, args: Dict[str, Any]): vuln_id = args.get('vulnerability_id') scan_results_id = args.get('scan_results_id') + sort_field = args.get('sort_field') + query_id = args.get('query_id') + sort_direction = args.get('sort_direction') + source_type = args.get('source_type', "individual") page = int(args.get('page', '0')) limit = int(args.get('limit', '50')) if limit > 200: @@ -1164,14 +1168,26 @@ def get_vulnerability_command(client: Client, args: Dict[str, Any]): }] query = { - 'scanID': scan_results_id, 'filters': vuln_filter, 'tool': 'vulndetails', 'type': 'vuln', 'startOffset': page, # Lower bound for the results list (must be specified) 'endOffset': page + limit # Upper bound for the results list (must be specified) + # 'sortField': sort_field, + # 'sortDir': sort_direction } + if source_type == 'individual': + if scan_results_id: + query['scanID'] = scan_results_id + else: + return_error("When choosing source_type = individual - scan_results_id must be provided.") + else: + if query_id: + query["id"] = query_id + else: + return_error(f"When choosing source_type = {source_type} - query_id must be provided.") + analysis = client.get_analysis(query, scan_results_id) if not analysis or 'response' not in analysis: diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index 4944fa2e3c3d..908be3b0dcf7 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -142,8 +142,44 @@ script: description: Scan results ID from the scan-report command isArray: false name: scan_results_id - required: true + required: false + secret: false + - default: false + description: Can be created via the Tenable.sc UI > Analysis > queries. can be retrieved from tenable-sc-list-query command. + isArray: false + name: query_id + required: false + secret: false + - default: false + description: Default is 'ASC'. Requires companion parameter, sort_field. + isArray: false + name: sort_direction + required: false + secret: false + auto: PREDEFINED + predefined: + - 'ASC' + - 'DESC' + defaultValue: 'ASC' + - default: false + description: Which field to sort by, For vulnerabilities data, Tenable recommends you sort by severity. + isArray: false + name: sort_field + required: false secret: false + defaultValue: severity + - default: false + description: When the source_type is "individual", a scan_results_id must be provided. cumulative — Analyzes cumulative vulnerabilities. patched — Analyzes mitigated vulnerabilities. + isArray: false + name: source_type + required: false + secret: false + auto: PREDEFINED + predefined: + - individual + - cumulative + - patched + defaultValue: individual - default: false defaultValue: '50' description: The number of objects to return in one response (maximum limit is 200). From 86122c6237930a0f2b04401f23e71a629325dd7d Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Tue, 9 May 2023 11:30:01 +0300 Subject: [PATCH 011/115] fixes --- Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index a4a661fd80d1..e541061cfadd 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -189,11 +189,11 @@ def create_scan(self, name, repo_id, policy_id, plugin_id, description, zone_id, body['schedule']['start'] = f"TZID={time_zone}:{start_time}" else: return_error("Please make sure to provide both time_zone and start_time.") - if all(repeat_rule_freq, repeat_rule_interval, repeat_rule_by_day): + if all([repeat_rule_freq, repeat_rule_interval, repeat_rule_by_day]): body['schedule']['repeatRule'] = f"FREQ={repeat_rule_freq};INTERVAL={repeat_rule_interval};BYDAY={repeat_rule_by_day}" elif repeat_rule_freq and repeat_rule_interval: body['schedule']['repeatRule'] = f"FREQ={repeat_rule_freq};INTERVAL={repeat_rule_interval}" - elif any(repeat_rule_freq, repeat_rule_interval, repeat_rule_by_day): + elif any([repeat_rule_freq, repeat_rule_interval, repeat_rule_by_day]): return_error("Please make sure to provide repeat_rule_freq, repeat_rule_interval with or without " "repeat_rule_by_day, or don't provide any of them.") body['schedule']['enabled'] = enabled From fc289d3a436c2ffef7beba91692c4836ffc091b0 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Tue, 9 May 2023 12:55:13 +0300 Subject: [PATCH 012/115] fixes --- .../Integrations/Tenable_sc/Tenable_sc.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index e541061cfadd..1d1cec292124 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -1161,25 +1161,24 @@ def get_vulnerability_command(client: Client, args: Dict[str, Any]): if limit > 200: limit = 200 - vuln_filter = [{ - 'filterName': 'pluginID', - 'operator': '=', - 'value': vuln_id - }] - query = { - 'filters': vuln_filter, 'tool': 'vulndetails', 'type': 'vuln', 'startOffset': page, # Lower bound for the results list (must be specified) - 'endOffset': page + limit # Upper bound for the results list (must be specified) - # 'sortField': sort_field, - # 'sortDir': sort_direction + 'endOffset': page + limit, # Upper bound for the results list (must be specified) + 'sortField': sort_field, + 'sortDir': sort_direction } if source_type == 'individual': if scan_results_id: query['scanID'] = scan_results_id + vuln_filter = [{ + 'filterName': 'pluginID', + 'operator': '=', + 'value': vuln_id + }] + query["filters"] = vuln_filter else: return_error("When choosing source_type = individual - scan_results_id must be provided.") else: From 15dcd81fc29ec71dc51f24b7fb4fb2a299adaf3a Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Tue, 9 May 2023 15:22:16 +0300 Subject: [PATCH 013/115] fixes --- .../Integrations/Tenable_sc/Tenable_sc.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 1d1cec292124..66aeff62ca0d 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -59,9 +59,9 @@ def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str if not self.token or not self.cookie: self.login() - def send_request_new(self, path, method='get', body=None, params=None, headers=None): + def send_request_new(self, path, method='get', body={}, params={}, headers=None): headers = headers or self.headers - return self._http_request(method, url_suffix=path, params=params, data=json.dumps(body)) + return self._http_request(method, url_suffix=path, params=params, data=json.dumps(body), headers=headers) def send_request_old(self, path, method='get', body=None, params=None, headers=None, try_number=1): body = body if body is not None else {} @@ -435,7 +435,7 @@ def change_scan_status(self, scan_results_id, status): return self.send_request(path, method='post') def get_vulnerability(self, vuln_id): - path = 'plugin/' + vuln_id + path = f'plugin/{vuln_id}' params = { 'fields': 'name,description,family,type,cpe,riskFactor,solution,synopsis,exploitEase,exploitAvailable,' @@ -821,7 +821,6 @@ def list_report_definitions_command(client: Client, args: Dict[str, Any]): def list_zones_command(client: Client, args: Dict[str, Any]): res = client.get_zones() - if not res or 'response' not in res: return_message('No zones found') @@ -832,17 +831,17 @@ def list_zones_command(client: Client, args: Dict[str, Any]): 'name': 'All Zones', 'description': '', 'ipList': '', - 'activeScanners': '' + 'scanners': '' }] - headers = ['ID', 'Name', 'Description', 'IPList', 'ActiveScanners'] + headers = ['ID', 'Name', 'Description', 'IPList', 'scanners'] mapped_zones = [{ 'ID': z['id'], 'Name': z['name'], 'Description': z['description'], 'IPList': z['ipList'], - 'ActiveScanners': z['activeScanners'] + 'scanners': z['scanners'] } for z in zones] demisto.results({ From 96c19acaeb11fc0df330cf8581b32b3f69196f80 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Wed, 10 May 2023 09:38:28 +0300 Subject: [PATCH 014/115] fixes --- .../Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 66aeff62ca0d..ecbf85d9128f 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -115,20 +115,23 @@ def login(self): token = login_response['response']['token'] self.token = str(token) - demisto.setIntegrationContext({'token': token}) + demisto.setIntegrationContext({'token': self.token}) def send_login_request(self, login_body): url = f'{self.url}/token' - headers = self.headers + headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + } res = self.session.request('post', url, headers=headers, data=json.dumps(login_body), verify=self.verify_ssl) if res.status_code < 200 or res.status_code >= 300: return_error(f'Error: Got status code {str(res.status_code)} with {url=} \ with body {res.content} with headers {str(res.headers)}') # type: ignore - cookie = res.cookies.get('TNS_SESSIONID', self.cookie) - demisto.setIntegrationContext({'cookie': cookie}) + self.cookie = res.cookies.get('TNS_SESSIONID', self.cookie) + demisto.setIntegrationContext({'cookie': self.cookie}) return res.json() From 91e957fba3c23cda40acaa956d43acbc8f385e95 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Wed, 10 May 2023 11:03:35 +0300 Subject: [PATCH 015/115] deprecate playbook --- Packs/Tenable_sc/Playbooks/playbook-tenable.sc-scan.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Packs/Tenable_sc/Playbooks/playbook-tenable.sc-scan.yml b/Packs/Tenable_sc/Playbooks/playbook-tenable.sc-scan.yml index 017ec4791b2b..0888052f7e57 100644 --- a/Packs/Tenable_sc/Playbooks/playbook-tenable.sc-scan.yml +++ b/Packs/Tenable_sc/Playbooks/playbook-tenable.sc-scan.yml @@ -1,7 +1,8 @@ id: tenable-sc-scan version: -1 name: Launch Scan - Tenable.sc -description: Launches an existing Tenable.sc scan by scan ID and waits for the scan to finish by polling its status in pre-defined intervals. +description: Deprecated. use tenable-sc-launch-scan-report command instead. +deprecated: true fromversion: 5.0.0 starttaskid: "0" tasks: From 684e4b2222de782fda6cb483475538e639797e55 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Wed, 10 May 2023 12:03:57 +0300 Subject: [PATCH 016/115] update list-zones --- .../Integrations/Tenable_sc/Tenable_sc.py | 28 +++++++++++++++---- .../Integrations/Tenable_sc/Tenable_sc.yml | 12 ++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index ecbf85d9128f..1162795fa806 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -826,7 +826,6 @@ def list_zones_command(client: Client, args: Dict[str, Any]): res = client.get_zones() if not res or 'response' not in res: return_message('No zones found') - zones = res['response'] if len(zones) == 0: zones = [{ @@ -834,25 +833,42 @@ def list_zones_command(client: Client, args: Dict[str, Any]): 'name': 'All Zones', 'description': '', 'ipList': '', - 'scanners': '' + 'activeScanners': '' }] - - headers = ['ID', 'Name', 'Description', 'IPList', 'scanners'] + headers = ['ID', 'Name', 'Description', 'IPList', 'activeScanners'] mapped_zones = [{ 'ID': z['id'], 'Name': z['name'], 'Description': z['description'], 'IPList': z['ipList'], - 'scanners': z['scanners'] + 'activeScanners': z['activeScanners'] } for z in zones] + hr = tableToMarkdown('Tenable.sc Scan Zones', mapped_zones, headers, removeNull=True) + + mapped_scanners_total = [] + for index, zone in enumerate(zones): + if scanners := zone.get('scanners'): + mapped_scanners = [{ + 'ID': s['id'], + 'Name': s['name'], + 'Description': s['description'], + 'Status': s['status'] + } for s in scanners] + mapped_zones[index]['Scanner'] = mapped_scanners + mapped_scanners_total.extend(mapped_scanners) + headers = ['ID', 'Name', 'Description', 'Status'] + + if mapped_scanners_total: + hr += tableToMarkdown('Tenable.sc Scanners', mapped_scanners_total, headers, removeNull=True) + demisto.results({ 'Type': entryTypes['note'], 'Contents': res, 'ContentsFormat': formats['json'], 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Tenable.sc Scan Zones', mapped_zones, headers, removeNull=True), + 'HumanReadable': hr, 'EntryContext': { 'TenableSC.ScanZone(val.ID===obj.ID)': createContext(mapped_zones, removeNull=True) } diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index 908be3b0dcf7..5f743228a1af 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -519,6 +519,18 @@ script: - contextPath: TenableSC.ScanZone.ActiveScanners description: Scan Zone active scanners type: number + - contextPath: TenableSC.ScanZone.Scanner.Name + description: Scanner name + type: string + - contextPath: TenableSC.ScanZone.Scanner.ID + description: Scanner ID + type: number + - contextPath: TenableSC.ScanZone.Scanner.Description + description: Scanner description + type: string + - contextPath: TenableSC.ScanZone.Scanner.Status + description: Scanner status + type: number - arguments: - default: false description: Scan name. From 991f5e92226d7dce56649f05c5b418d3bc533ce4 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Wed, 10 May 2023 15:12:57 +0300 Subject: [PATCH 017/115] added tenable-sc-list-groups command --- .../Integrations/Tenable_sc/Tenable_sc.py | 53 ++++- .../Integrations/Tenable_sc/Tenable_sc.yml | 225 +++++++++++------- 2 files changed, 189 insertions(+), 89 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 1162795fa806..90a4a58ea530 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -437,6 +437,13 @@ def change_scan_status(self, scan_results_id, status): return self.send_request(path, method='post') + def list_groups(self, show_users): + params = {} + if show_users: + params['fields'] = 'users' + + return self.send_request(path='group', method='get', params=params) + def get_vulnerability(self, vuln_id): path = f'plugin/{vuln_id}' @@ -1687,6 +1694,49 @@ def fetch_incidents(client: Client, first_fetch: str = '3 days'): demisto.setLastRun({'time': max_timestamp}) +def list_groups_command(client: Client, args: Dict[str, Any]): + show_users = argToBoolean(args.get("show_users", True)) + limit = int(args.get('limit', '50')) + res = client.list_groups(show_users) + if len(res) > limit: + res = res[:limit] + if not res or not res.get('response', []): + return_message('No groups found') + groups = res.get('response', []) + mapped_groups = [{ + 'ID': group.get('id'), + 'Name': group.get('name'), + 'Description': group.get('description') + } for group in groups] + headers = ['ID', 'Name', 'Description'] + hr = tableToMarkdown('Tenable.sc groups', mapped_groups, headers, removeNull=True) + if show_users: + headers = ['Username', 'Firstname', 'Lastname'] + users = [] + for index, group in enumerate(groups): + users = [{ + 'Username': user['username'], + 'Firstname': user['firstname'], + 'Lastname': user['lastname'], + 'ID': user['id'], + 'UUID': user['uuid'] + } for user in group.get('users')] + mapped_groups[index]['Users'] = users + group_id = group.get('id') + hr += f"{tableToMarkdown(f'Group id:{group_id}', users, headers, removeNull=True)}\n" + groups = mapped_groups + demisto.results({ + 'Type': entryTypes['note'], + 'Contents': res, + 'ContentsFormat': formats['json'], + 'ReadableContentsFormat': formats['markdown'], + 'HumanReadable': hr, + 'EntryContext': { + 'TenableSC.Group(val.ID===obj.ID)': createContext(groups, removeNull=True) + } + }) + + def get_all_scan_results_command(client: Client, args: Dict[str, Any]): res = client.get_all_scan_results() get_manageable_results = args.get('manageable', 'false').lower() # 'true' or 'false' @@ -1787,7 +1837,8 @@ def main(): 'tenable-sc-get-alert': get_alert_command, 'tenable-sc-get-system-information': get_system_information_command, 'tenable-sc-get-system-licensing': get_system_licensing_command, - 'tenable-sc-get-all-scan-results': get_all_scan_results_command + 'tenable-sc-get-all-scan-results': get_all_scan_results_command, + 'tenable-sc-list-groups': list_groups_command } try: diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index 5f743228a1af..29b28a81d411 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -819,67 +819,67 @@ script: required: true secret: false deprecated: false - description: Create an Asset in Tenable.sc with provided IP addresses + description: Create an Asset in Tenable.sc with provided IP addresses. execution: false name: tenable-sc-create-asset outputs: - contextPath: TenableSC.Asset.Name - description: Asset Name + description: Asset Name. type: string - contextPath: TenableSC.Asset.ID - description: Asset ID + description: Asset ID. type: string - contextPath: TenableSC.Asset.OwnerName - description: Asset owner name + description: Asset owner name. type: string - contextPath: TenableSC.Asset.Tags - description: Asset tags + description: Asset tags. type: string - arguments: - default: false - description: Asset ID that can be retrieved from the list-assets command + description: Asset ID that can be retrieved from the list-assets command. isArray: false name: asset_id required: true secret: false deprecated: false - description: Get details for a given asset in Tenable.sc + description: Get details for a given asset in Tenable.sc. execution: false name: tenable-sc-get-asset outputs: - contextPath: TenableSC.Asset.ID - description: Asset ID + description: Asset ID. type: number - contextPath: TenableSC.Asset.Name - description: Asset name + description: Asset name. type: string - contextPath: TenableSC.Asset.Description - description: Asset description + description: Asset description. type: string - contextPath: TenableSC.Asset.Tag - description: Asset tag + description: Asset tag. type: string - contextPath: TenableSC.Asset.Modified - description: Asset last modified time + description: Asset last modified time. type: date - contextPath: TenableSC.Asset.Owner - description: Asset owner user name + description: Asset owner user name. type: string - contextPath: TenableSC.Asset.Group - description: Asset owner group + description: Asset owner group. type: string - contextPath: TenableSC.Asset.IPs - description: Asset viewable IPs + description: Asset viewable IPs. type: unknown - arguments: - default: false - description: Asset ID + description: Asset ID. isArray: false name: asset_id required: true secret: false deprecated: false - description: Delete the Asset with the given ID from Tenable.sc + description: Delete the Asset with the given ID from Tenable.sc. execution: true name: tenable-sc-delete-asset - arguments: @@ -895,216 +895,216 @@ script: required: false secret: false deprecated: false - description: List alerts from Tenable.sc + description: List alerts from Tenable.sc. execution: false name: tenable-sc-list-alerts outputs: - contextPath: TenableSC.Alert.ID - description: Alert ID + description: Alert ID. type: string - contextPath: TenableSC.Alert.Name - description: Alert name + description: Alert name. type: string - contextPath: TenableSC.Alert.Description - description: Alert description + description: Alert description. type: string - contextPath: TenableSC.Alert.State - description: Alert state + description: Alert state. type: string - contextPath: TenableSC.Alert.Actions - description: Alert Actions + description: Alert Actions. type: string - contextPath: TenableSC.Alert.LastTriggered - description: Alert last triggered time + description: Alert last triggered time. type: date - contextPath: TenableSC.Alert.LastEvaluated - description: Alert last evaluated time + description: Alert last evaluated time. type: date - contextPath: TenableSC.Alert.Group - description: Alert owner group name + description: Alert owner group name. type: string - contextPath: TenableSC.Alert.Owner - description: Alert owner user name + description: Alert owner user name. type: string - arguments: - default: false - description: Alert ID, can be retrieved from list-alerts command + description: Alert ID, can be retrieved from list-alerts command. isArray: false name: alert_id required: true secret: false deprecated: false - description: Get information about a given alert in Tenable.sc + description: Get information about a given alert in Tenable.sc. execution: false name: tenable-sc-get-alert outputs: - contextPath: TenableSC.Alert.ID - description: Alert ID + description: Alert ID. type: string - contextPath: TenableSC.Alert.Name - description: Alert name + description: Alert name. type: string - contextPath: TenableSC.Alert.Description - description: Alert description + description: Alert description. type: string - contextPath: TenableSC.Alert.State - description: Alert state + description: Alert state. type: string - contextPath: TenableSC.Alert.Condition.Trigger - description: Alert trigger + description: Alert trigger. type: string - contextPath: TenableSC.Alert.LastTriggered - description: Alert last triggered time + description: Alert last triggered time. type: date - contextPath: TenableSC.Alert.Condition.Query - description: Alert query name + description: Alert query name. type: string - contextPath: TenableSC.Alert.Condition.Filter.Name - description: Alert query filter name + description: Alert query filter name. type: string - contextPath: TenableSC.Alert.Condition.Filter.Values - description: Alert query filter values + description: Alert query filter values. type: Unknown - contextPath: TenableSC.Alert.Action.Type - description: Alert action type + description: Alert action type. type: string - contextPath: TenableSC.Alert.Action.Values - description: Alert action values + description: Alert action values. type: Unknown - arguments: - default: false - description: A valid IP address of a device + description: A valid IP address of a device. isArray: false name: ip required: false secret: false - default: false - description: DNS name of a device + description: DNS name of a device. isArray: false name: dns_name required: false secret: false - default: false - description: Repository ID to get the device from, can be retrieved from list-repositories command + description: Repository ID to get the device from, can be retrieved from list-repositories command. isArray: false name: repository_id required: false secret: false deprecated: false - description: Gets the specified device information + description: Gets the specified device information. execution: false name: tenable-sc-get-device outputs: - contextPath: TenableSC.Device.IP - description: Device IP address + description: Device IP address. type: string - contextPath: TenableSC.Device.UUID - description: Device UUID + description: Device UUID. type: string - contextPath: TenableSC.Device.RepositoryID - description: Device repository ID + description: Device repository ID. type: string - contextPath: TenableSC.Device.MacAddress - description: Device Mac address + description: Device Mac address. type: string - contextPath: TenableSC.Device.NetbiosName - description: Device Netbios name + description: Device Netbios name. type: string - contextPath: TenableSC.Device.DNSName - description: Device DNS name + description: Device DNS name. type: string - contextPath: TenableSC.Device.OS - description: Device Operating System + description: Device Operating System. type: string - contextPath: TenableSC.Device.OsCPE - description: Device Common Platform Enumeration + description: Device Common Platform Enumeration. type: string - contextPath: TenableSC.Device.LastScan - description: Device's last scan time + description: Device's last scan time. type: date - contextPath: TenableSC.Device.RepositoryName - description: Device repository name + description: Device repository name. type: string - contextPath: TenableSC.Device.TotalScore - description: Device total threat score + description: Device total threat score. type: number - contextPath: TenableSC.Device.LowSeverity - description: Device total threat scores with low severity + description: Device total threat scores with low severity. type: number - contextPath: TenableSC.Device.MediumSeverity - description: Device total threat scores with medium severity + description: Device total threat scores with medium severity. type: number - contextPath: TenableSC.Device.HighSeverity - description: Device total threat scores with high severity + description: Device total threat scores with high severity. type: number - contextPath: TenableSC.Device.CriticalSeverity - description: Device total threat scores with critical severity + description: Device total threat scores with critical severity. type: number - contextPath: Endpoint.IPAddress - description: Endpoint IP address + description: Endpoint IP address. type: string - contextPath: Endpoint.Hostname - description: Endpoint DNS name + description: Endpoint DNS name. type: string - contextPath: Endpoint.MACAddress - description: Endpoint mac address + description: Endpoint mac address. type: string - contextPath: Endpoint.OS - description: Endpoint OS + description: Endpoint OS. type: string - arguments: - default: false - description: Filter by user ID + description: Filter by user ID. isArray: false name: id required: false secret: false - default: false - description: Filter by user username + description: Filter by user username. isArray: false name: username required: false secret: false - default: false - description: Filter by user email address + description: Filter by user email address. isArray: false name: email required: false secret: false deprecated: false - description: List users in Tenable.sc + description: List users in Tenable.sc. execution: false name: tenable-sc-list-users outputs: - contextPath: TenableSC.User.ID - description: User ID + description: User ID. type: string - contextPath: TenableSC.User.Username - description: Username + description: Username. type: string - contextPath: TenableSC.User.FirstName - description: User first name + description: User first name. type: string - contextPath: TenableSC.User.LastName - description: User last name + description: User last name. type: string - contextPath: TenableSC.User.Title - description: User title + description: User title. type: string - contextPath: TenableSC.User.Email - description: User email address + description: User email address. type: string - contextPath: TenableSC.User.Created - description: The creation time of the user + description: The creation time of the user. type: date - contextPath: TenableSC.User.Modified - description: Last modification time of the user + description: Last modification time of the user. type: date - contextPath: TenableSC.User.Login - description: User last login + description: User last login. type: date - contextPath: TenableSC.User.Role - description: User role name + description: User role name. type: string - deprecated: false description: Retrieve licensing information from Tenable.sc @@ -1118,39 +1118,39 @@ script: description: Number of licensed IP addresses type: Unknown - contextPath: TenableSC.Status.License - description: License status + description: License status. type: Unknown - deprecated: false - description: Get the system information and diagnostics from Tenable.sc + description: Get the system information and diagnostics from Tenable.sc. execution: false name: tenable-sc-get-system-information outputs: - contextPath: TenableSC.System.Version - description: System version + description: System version. type: string - contextPath: TenableSC.System.BuildID - description: System build ID + description: System build ID. type: string - contextPath: TenableSC.System.ReleaseID - description: System release ID + description: System release ID. type: string - contextPath: TenableSC.System.License - description: System license status + description: System license status. type: string - contextPath: TenableSC.System.JavaStatus - description: Server java status + description: Server java status. type: boolean - contextPath: TenableSC.System.RPMStatus - description: Server RPM status + description: Server RPM status. type: boolean - contextPath: TenableSC.System.DiskStatus - description: Server disk status + description: Server disk status. type: boolean - contextPath: TenableSC.System.DiskThreshold - description: System left space on disk + description: System left space on disk. type: number - contextPath: TenableSC.System.LastCheck - description: System last check time + description: System last check time. type: date - arguments: - auto: PREDEFINED @@ -1227,6 +1227,55 @@ script: - contextPath: TenableSC.ScanResults.RepositoryName description: Scan repository name. type: string + - arguments: + - auto: PREDEFINED + default: false + defaultValue: 'true' + description: Wether to show group member or not. Default is True. + isArray: false + name: show_users + predefined: + - 'true' + - 'false' + required: false + secret: false + - default: false + defaultValue: '50' + description: The number of objects to return in one response. Default is 50. + isArray: false + name: limit + required: false + secret: false + deprecated: false + description: list all groups. + execution: false + name: tenable-sc-list-groups + outputs: + - contextPath: TenableSC.Group.Name + description: Group name. + type: string + - contextPath: TenableSC.Group.ID + description: Group ID. + type: number + - contextPath: TenableSC.Group.Description + description: Group description. + type: string + - contextPath: TenableSC.Group.Users.Firstname + description: Group's user's first name. + type: string + - contextPath: TenableSC.Group.Users.Lastname + description: Group's user's last name. + type: string + - contextPath: TenableSC.Group.Users.ID + description: Group's user's id. + type: string + - contextPath: TenableSC.Group.Users.UUID + description: Group's user's uuid. + type: string + - contextPath: TenableSC.Group.Users.Username + description: Group's user's user name. + type: string + isfetch: true runonce: false script: '-' From adfc83d54cffa95815a75a3787ab91228f6de3f4 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Wed, 10 May 2023 16:03:26 +0300 Subject: [PATCH 018/115] adding tenable-sc-create-user command --- .../Integrations/Tenable_sc/Tenable_sc.yml | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index 29b28a81d411..695247076326 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -1276,6 +1276,151 @@ script: description: Group's user's user name. type: string + - arguments: + - default: false + description: + isArray: false + name: first_name + required: false + secret: false + - default: false + description: + isArray: false + name: last_name + required: false + secret: false + - default: false + description: + isArray: false + name: user_name + required: true + secret: false + - default: false + description: + isArray: false + name: email + required: false + secret: false + - default: false + description: + isArray: false + name: address + required: false + secret: false + - default: false + description: + isArray: false + name: phone + required: false + secret: false + - default: false + description: + isArray: false + name: city + required: false + secret: false + - default: false + description: + isArray: false + name: state + required: false + secret: false + - default: false + description: + isArray: false + name: country + required: false + secret: false + - auto: PREDEFINED + default: false + defaultValue: 'false' + description: + isArray: false + name: locked + predefined: + - 'true' + - 'false' + required: false + secret: false + - default: false + description: + isArray: false + name: email_notice + required: false + secret: false + auto: PREDEFINED + predefined: + - 'both' + - 'password' + - 'id' + - default: false + description: Tenable (TNS). Lightweight Directory Access Protocol (LDAP). Security Assertion Markup Language (SAML). LDAP server or SAML authentication need to be configured in order to select LDAP or SAML. + isArray: false + name: auth_type + required: false + secret: false + auto: PREDEFINED + defaultValue: 'tns' + predefined: + - 'Ldap' + - 'legacy' + - 'linked' + - 'saml' + - 'tns' + - default: false + description: Password must be at least 3 characters. + isArray: false + name: password + required: true + secret: false + - default: false + description: + isArray: false + name: time_zone + required: false + secret: false + - default: false + description: + isArray: false + name: role_id + required: true + secret: false + - default: false + description: + isArray: false + name: must_change_password + required: false + secret: false + - default: false + description: + isArray: false + name: managed_users_groups + required: false + secret: false + - default: false + description: + isArray: false + name: managed_objects_groups + required: false + secret: false + - default: false + description: + isArray: false + name: group_id + required: true + secret: false + - default: false + description: + isArray: false + name: responsible_asset_id + required: true + secret: false + deprecated: false + description: Creates a new user. + execution: false + name: tenable-sc-create-user + outputs: + isfetch: true runonce: false script: '-' From 4f04f49b2702624bd50d2a478d64b2c64861c8f8 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Sun, 14 May 2023 17:52:17 +0300 Subject: [PATCH 019/115] in progress --- .../Integrations/Tenable_sc/Tenable_sc.py | 385 +++++++++++++++++- .../Integrations/Tenable_sc/Tenable_sc.yml | 381 ++++++++++++++++- 2 files changed, 732 insertions(+), 34 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 90a4a58ea530..92d7cb59e2d0 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -79,7 +79,7 @@ def send_request_old(self, path, method='get', body=None, params=None, headers=N if res.status_code == 403 and try_number <= self.max_retries: self.login() headers['X-SecurityCenter'] = self.token # The Token is being updated in the login - return self.send_request(path, method, body, params, headers, try_number + 1) + return self.send_request_old(path, method, body, params, headers, try_number + 1) elif res.status_code < 200 or res.status_code >= 300: try: @@ -193,7 +193,8 @@ def create_scan(self, name, repo_id, policy_id, plugin_id, description, zone_id, else: return_error("Please make sure to provide both time_zone and start_time.") if all([repeat_rule_freq, repeat_rule_interval, repeat_rule_by_day]): - body['schedule']['repeatRule'] = f"FREQ={repeat_rule_freq};INTERVAL={repeat_rule_interval};BYDAY={repeat_rule_by_day}" + body['schedule']['repeatRule'] = f"FREQ={repeat_rule_freq};INTERVAL={repeat_rule_interval};" + f"BYDAY={repeat_rule_by_day}" elif repeat_rule_freq and repeat_rule_interval: body['schedule']['repeatRule'] = f"FREQ={repeat_rule_freq};INTERVAL={repeat_rule_interval}" elif any([repeat_rule_freq, repeat_rule_interval, repeat_rule_by_day]): @@ -249,10 +250,18 @@ def launch_scan(self, scan_id, scan_target): return self.send_request(path, 'post', body=body) def get_query(self, query_id): - path = 'query/' + query_id + path = f'query/{query_id}' return self.send_request(path) + def list_queries(self, type): + path = 'query' + params = {} + if type: + params["type"] = type + + return self.send_request(path=path, method="GET", params=params) + def get_all_scan_results(self): params = { 'fields': 'name,description,details,status,scannedIPs,startTime,scanDuration,importStart,' @@ -488,10 +497,140 @@ def get_users(self, fields, user_id): return self.send_request(path, params=params) + def create_user(self, args): + body = create_user_request_body(args) + + return self.send_request(path='user', body=body, method='POST') + + def update_user(self, args, user_id): + body = create_user_request_body(args) + + return self.send_request(path=f'user/{user_id}', body=body, method='PATCH') + + def delete_user(self, user_id): + return self.send_request(path=f'user/{user_id}', method='DELETE') + + def list_plugin_family(self, plugin_id, is_active): + path = "pluginFamily" + if plugin_id: + path += f"/{plugin_id}" + else: + if is_active == 'true': + path += "?fields=active" + elif is_active == 'false': + path += "?fields=passive" + return self.send_request(path=path, method='GET') + + def create_policy(self, policy_name, policy_description, policy_template_id, port_scan_range, tcp_scanner, syn_scanner, + udp_scanner, syn_firewall_detection, family_id, plugins_id): + body = { + "name": policy_name, + "description": policy_description, + "context": "scan", + "families": [{"id": family_id, "plugins": [{"id": id for id in plugins_id.split(',')}]}], + "preferences": { + "portscan_range": port_scan_range, + "tcp_scanner": tcp_scanner, + "syn_scanner": syn_scanner, + "udp_scanner": udp_scanner, + "syn_firewall_detection": syn_firewall_detection + }, + "policyTemplate": { + "policy_template_id": policy_template_id + }, + } + return self.send_request(path="policy", method='POST', body=body) + + # def update_user(self, first_name, last_name, user_name, email, address, phone, city, state, country, locked, + # time_zone, must_change_password, role_id, managed_users_groups, managed_objects_groups, + # group_id, responsible_asset_id, user_id, current_password): + # body["preferences"] = [] + + # if first_name: + # body["firstname"] = first_name + # if last_name: + # body["lastname"] = last_name + # if user_name: + # body["username"] = user_name + # if email: + # body["email"] = email + # if city: + # body["city"] = city + # if state: + # body["state"] = state + # if address: + # body["address"] = address + # if country: + # body["country"] = country + # if role_id: + # body["roleID"] = role_id + # if phone: + # body["phone"] = phone + # if locked: + # body["locked"] = locked + # if time_zone: + # body["preferences"].append({"name": "timezone", "value": time_zone, "tag": ""}) + # if must_change_password: + # body["mustChangePassword"] = must_change_password + # if current_password: + # body[""] + + # if role_id and role_id != 1: + # body["groupID"] = group_id + # body["responsibleAssetID"] = responsible_asset_id + # elif role_id and role_id == 1: + # body["managedUsersGroups"] = [{"id": int(managed_users_group)} for managed_users_group in managed_users_groups] + # body["managedObjectsGroups"] = [{"id": int(managed_objects_group)} for + # managed_objects_group in managed_objects_groups] + + # return self.send_request(path='user', body=body, method='PATCH') + ''' HELPER FUNCTIONS ''' +def capitalize_first_letter(str): + if str == 'id': + return 'ID' + else: + return str[:1].upper() + str[1:] + + +def create_user_request_body(args): + user_query_mapping_dict: dict[str, str] = { + "firstname": "first_name", + "lastname": "last_name", + "username": "user_name", + "email": "email", + "city": "city", + "state": "state", + "address": "address", + "country": "country", + "authType": "auth_type", + "roleID": "role_id", + "emailNotice": "email_notice", + "phone": "phone", + "locked": "locked", + "mustChangePassword": "must_change_password", + "currentPassword": "current_password" + } + body = {key: args.get(value) for key, value in user_query_mapping_dict.items() if args.get(value)} + + if role_id := args.get('role_id'): + if role_id != 1: + body["groupID"] = args.get('group_id') + body["responsibleAssetID"] = args.get('responsible_asset_id') + else: + body["managedUsersGroups"] = [{"id": int(managed_users_group)} for + managed_users_group in args.get('managed_users_groups')] + body["managedObjectsGroups"] = [{"id": int(managed_objects_group)} for + managed_objects_group in args.get('managed_objects_groups')] + if time_zone := args.get('time_zone'): + body["preferences"].append([{"name": "timezone", "value": time_zone, "tag": ""}]) + + return body + + def get_server_url(url): url = re.sub('/[\/]+$/', '', url) url = re.sub('\/$', '', url) @@ -503,6 +642,54 @@ def return_message(msg): sys.exit(0) +def validate_user_body_params(args, command_type): + numbers_args_ls = ["group_id", "user_id", "responsible_asset_id"] + + time_zone = args.get("time_zone") + password = args.get("password") + email_notice = args.get("email_notice") + email = args.get("email") + auth_type = args.get("auth_type") + + for number_arg in numbers_args_ls: + try: + int(args.get(number_arg, '0')) + except Exception: + return_error(f"{number_arg} must be a valid number.") + + if time_zone and time_zone not in pytz.all_timezones: + return_error("Invalid time zone ID. Please choose one of the following: " + "https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html") + + if command_type == "create" and (auth_type == 'Ldap' or auth_type == 'saml') and args.get("must_change_password"): + return_error(f"When choosing {auth_type=}, must_change_password must be set to False.") + + if password: + if command_type == 'update' and not args.get("current_password"): + return_error("current_password must be provided when attempting to update password.") + if len(password) < 3: + return_error("Password length must be at least 3 characters.") + + if email and not re.compile(emailRegex).match(email): + return_error(f"Error: The given email address: {email} is not valid") + + if command_type == 'create' and not email_notice == 'None' and not email: + return_error("When email_notice is different from None, an email must be given as well.") + + +def timestamp_to_utc(timestamp_str, default_returned_value=''): + if timestamp_str and (int(timestamp_str) > 0): # no value is when timestamp_str == '-1' + return datetime.utcfromtimestamp(int(timestamp_str)).strftime( + '%Y-%m-%dT%H:%M:%SZ') + return default_returned_value + + +def scan_duration_to_demisto_format(duration, default_returned_value=''): + if duration: + return float(duration) / 60 + return default_returned_value + + ''' FUNCTIONS ''' @@ -1786,17 +1973,183 @@ def get_all_scan_results_command(client: Client, args: Dict[str, Any]): }) -def timestamp_to_utc(timestamp_str, default_returned_value=''): - if timestamp_str and (int(timestamp_str) > 0): # no value is when timestamp_str == '-1' - return datetime.utcfromtimestamp(int(timestamp_str)).strftime( - '%Y-%m-%dT%H:%M:%SZ') - return default_returned_value +def create_user_command(client: Client, args: Dict[str, Any]): + validate_user_body_params(args, "create") + res = client.create_user(args) + print(res) -def scan_duration_to_demisto_format(duration, default_returned_value=''): - if duration: - return float(duration) / 60 - return default_returned_value +def update_user_command(client: Client, args: Dict[str, Any]): + # must_change_password = argToBoolean(args.get('must_change_password', True)) + # locked = argToBoolean(args.get('locked', False)) + # managed_users_groups = args.get('managed_users_groups', '0').split(',') + # managed_objects_groups = args.get('managed_objects_groups', '0').split(',') + user_id = args.get('user_id') + validate_user_body_params(args, "update") + res = client.update_user(args, user_id) + print(res) + + +def delete_user_command(client: Client, args: Dict[str, Any]): + user_id = args.get('user_id') + client.delete_user(user_id) + return demisto.results({ + 'HumanReadable': f"User {user_id} is deleted." + }) + + +def list_plugin_family_command(client: Client, args: Dict[str, Any]): + is_active = args.get('is_active') + limit = int(args.get('limit', '50')) + plugin_id = args.get('plugin_id', '') + res = client.list_plugin_family(plugin_id, is_active) + if not res or not res.get('response', []): + return_message('No plugins found') + plugins = res.get('response') + if isinstance(plugins, dict): + plugins = [plugins] + is_active = "false" if plugins.get("type") == "passive" else "true" + if len(plugins) > limit: + plugins = plugins[:limit] + mapped_plugins = [{"Plugin ID": plugin.get("id"), "Plugin Name": plugin.get("name")} for plugin in plugins] + if is_active: + for mapped_plugin in mapped_plugins: + mapped_plugin["Is Active"] = is_active + headers = ["Plugin ID", "Plugin Name", "Is Active"] + demisto.results({ + 'Type': entryTypes['note'], + 'Contents': res, + 'ContentsFormat': formats['json'], + 'ReadableContentsFormat': formats['markdown'], + 'HumanReadable': tableToMarkdown('Plugin families:', mapped_plugins, headers, removeNull=True), + 'EntryContext': { + 'TenableSC.PluginFamily(val.ID===obj.ID)': createContext(plugins, removeNull=True, + keyTransform=capitalize_first_letter) + } + }) + + +def create_policy_command(client: Client, args: Dict[str, Any]): + policy_name = args.get("policy") + policy_description = args.get("policy_description") + policy_template_id = args.get("policy_template_id", '1') + port_scan_range = args.get("port_scan_range", 'default') + tcp_scanner = args.get("tcp_scanner") + syn_scanner = args.get("syn_scanner") + udp_scanner = args.get("udp_scanner") + syn_firewall_detection = args.get("syn_firewall_detection", 'Automatic (normal)') + family_id = args.get("family_id") + plugins_id = args.get("plugins_id") + res = client.create_policy(policy_name, policy_description, policy_template_id, port_scan_range, tcp_scanner, syn_scanner, + udp_scanner, syn_firewall_detection, family_id, plugins_id) + created_policy = res.get("response") + mapped_created_policy = { + # "Policy type": created_policy.get(""), + "Policy ID": created_policy.get("id"), + "name": created_policy.get("name"), + "Description": created_policy.get("description"), + "Created Time": created_policy.get("createdTime"), + # "Plugin Families": created_policy.get(""), + "Policy Status": created_policy.get("status"), + "Policy UUID": created_policy.get("uuid"), + "Policy can Manage": created_policy.get("canManage"), + "Creator Username": created_policy.get("creator", {}).get("username"), + # "Owner Username": created_policy.get(""), + "policyTemplate ID": created_policy.get("policyTemplate", {}).get("id"), + "policyTemplate Name": created_policy.get("policyTemplate", {}).get("name") + } + headers = ["Policy type", "Policy Id", "name", "Description", "Created Time", "Plugin Families", "Policy Status", + "Policy UUID", "Policy can Manage", "Creator Username", "Owner Username", "policyTemplate id", + "policyTemplate Name"] + demisto.results({ + 'Type': entryTypes['note'], + 'Contents': res, + 'ContentsFormat': formats['json'], + 'ReadableContentsFormat': formats['markdown'], + 'HumanReadable': tableToMarkdown('Policy was created successfully:', mapped_created_policy, headers, removeNull=True), + 'EntryContext': { + 'TenableSC.Query(val.ID===obj.ID)': createContext(created_policy, removeNull=True, + keyTransform=capitalize_first_letter) + } + }) + + +def list_query_command(client: Client, args: Dict[str, Any]): + type = args.get('type') + limit = int(args.get('limit', '50')) + query_id = args.get('query_id', '') + if query_id: + res, hr, ec = get_query(client, query_id) + else: + res, hr, ec = list_queries(client, type, limit) + demisto.results({ + 'Type': entryTypes['note'], + 'Contents': res, + 'ContentsFormat': formats['json'], + 'ReadableContentsFormat': formats['markdown'], + 'HumanReadable': hr, + 'EntryContext': { + 'TenableSC.Query(val.ID===obj.ID)': createContext(ec, removeNull=True, keyTransform=capitalize_first_letter) + } + }) + + +def get_query(client: Client, query_id): + res = client.get_query(query_id) + if not res or not res.get('response', []): + return_message(f"The query {query_id} wasn't found") + query = res.get('response') + mapped_query = { + "Query Id": query_id, + "Query Name": query.get("name"), + "Query Description": query.get("description"), + "Query Filters": query.get("filters") + } + headers = ["Query Id", "Query Name", "Query Description", "Query Filters"] + hr = tableToMarkdown(f'Query {query_id}', mapped_query, headers, removeNull=True) + return res, hr, query + + +def list_queries(client: Client, type, limit): + res = client.list_queries(type) + if not res or not res.get('response', []): + return_message("No queries found.") + queries = res.get('response') + manageable_queries = queries.get("manageable", []) + usable_queries = queries.get("usable_queries", []) + mapped_queries, mapped_usable_queries = [], [] + found_ids = [] + + for manageable_query in manageable_queries: + query_id = manageable_query.get("id") + mapped_queries.append({ + "Query Id": query_id, + "Query Name": manageable_query.get("name"), + "Query Description": manageable_query.get("description"), + "Query Filters": manageable_query.get("filters"), + "Query Manageable": "True" + }) + found_ids.append(query_id) + + for usable_query in usable_queries: + query_id = usable_query.get("id") + if query_id not in found_ids: + mapped_usable_queries.append({ + "Query Id": usable_query.get("id"), + "Query Name": usable_query.get("name"), + "Query Description": usable_query.get("description"), + "Query Filters": usable_query.get("filters"), + "Query Usable": "True" + }) + else: + for mapped_query in mapped_queries: + if query_id == mapped_query["Query Id"]: + mapped_query["Query Usable"] = "True" + + mapped_queries.extend(mapped_usable_queries) + headers = ["Query Id", "Query Name", "Query Description", "Query Filters", "Query Manageable", "Query Usable"] + hr = tableToMarkdown('Queries:', mapped_queries, headers, removeNull=True) + return res, hr, queries def main(): @@ -1838,7 +2191,13 @@ def main(): 'tenable-sc-get-system-information': get_system_information_command, 'tenable-sc-get-system-licensing': get_system_licensing_command, 'tenable-sc-get-all-scan-results': get_all_scan_results_command, - 'tenable-sc-list-groups': list_groups_command + 'tenable-sc-list-groups': list_groups_command, + 'tenable-sc-create-user': create_user_command, + 'tenable-sc-update-user': update_user_command, + 'tenable-sc-delete-user': delete_user_command, + 'tenable-sc-list-plugin-family': list_plugin_family_command, + 'tenable-sc-create-policy': create_policy_command, + 'tenable-sc-list-query': list_query_command } try: diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index 695247076326..40a66ee06d60 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -662,7 +662,7 @@ script: required: false secret: false - default: false - description: 'The timezone for the given start_time, possible values can be found here: https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html..' + description: 'The timezone for the given start_time, possible values can be found here: https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html.' isArray: false name: time_zone required: false @@ -1278,55 +1278,55 @@ script: - arguments: - default: false - description: + description: The user's first name. isArray: false name: first_name required: false secret: false - default: false - description: + description: The user's last name. isArray: false name: last_name required: false secret: false - default: false - description: + description: The user's username. isArray: false name: user_name required: true secret: false - default: false - description: + description: The user's email address. Required if email_notice is given. isArray: false name: email required: false secret: false - default: false - description: + description: The user's postal address. isArray: false name: address required: false secret: false - default: false - description: + description: The user's phone number. isArray: false name: phone required: false secret: false - default: false - description: + description: The city the user is living in. isArray: false name: city required: false secret: false - default: false - description: + description: The state the user is living in. isArray: false name: state required: false secret: false - default: false - description: + description: The country the user is living in. isArray: false name: country required: false @@ -1334,7 +1334,7 @@ script: - auto: PREDEFINED default: false defaultValue: 'false' - description: + description: Default is False. Wether the user should be locked or not. isArray: false name: locked predefined: @@ -1343,21 +1343,23 @@ script: required: false secret: false - default: false - description: + description: If different from None, a valid email address must be given. isArray: false name: email_notice required: false secret: false auto: PREDEFINED + defaultValue: 'None' predefined: - 'both' - 'password' - 'id' + - 'None' - default: false description: Tenable (TNS). Lightweight Directory Access Protocol (LDAP). Security Assertion Markup Language (SAML). LDAP server or SAML authentication need to be configured in order to select LDAP or SAML. isArray: false name: auth_type - required: false + required: true secret: false auto: PREDEFINED defaultValue: 'tns' @@ -1368,50 +1370,69 @@ script: - 'saml' - 'tns' - default: false - description: Password must be at least 3 characters. + description: The user's password. Must be at least 3 characters. isArray: false name: password required: true secret: false - default: false - description: + description: 'The user timezone, possible values can be found here: https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html.' isArray: false name: time_zone required: false secret: false - default: false - description: + description: "The user's role. Should be a number between 1 to 7. Role description: 1- Administrator, 2- Security Manager, 3-Security Analyst, 4-Vulnerability Analyst, 5-Executive, 6-Credential Manager, 7-Auditor. Only an Administrator can create Administrator accounts." isArray: false name: role_id required: true secret: false + auto: PREDEFINED + predefined: + - '0' + - '1' + - '2' + - '3' + - '4' + - '5' + - '6' + - '7' - default: false - description: + description: When choosing LDAP or SAML auth types, 'must_change_password' must be set to False. For all other cases can be either True or False. isArray: false name: must_change_password required: false secret: false + auto: PREDEFINED + defaultValue: 'false' + predefined: + - 'false' + - 'true' - default: false - description: + description: Comma separated list of session user's role can manage group. Use tenable-sc-list-groups to get all available groups. Default is 0. isArray: false + defaultValue: 0 name: managed_users_groups required: false secret: false - default: false - description: + description: Comma separated list of session user's role can manage group. Use tenable-sc-list-groups to get all available groups. Default is 0. isArray: false + defaultValue: 0 name: managed_objects_groups required: false secret: false - default: false - description: + description: Default is 0. Valid group ID whose users can be managed by created user. isArray: false name: group_id + defaultValue: 0 required: true secret: false - default: false - description: + description: Default is 0. ID of a valid, usable, accessible asset. Use tenable-sc-list-assets to get all available assets. -1 is not set, 0 is all assets, and other numbers are asset id. isArray: false + defaultValue: 0 name: responsible_asset_id required: true secret: false @@ -1421,6 +1442,324 @@ script: name: tenable-sc-create-user outputs: + - arguments: + - default: false + description: The user's first name. + isArray: false + name: first_name + required: false + secret: false + - default: false + description: The user's last name. + isArray: false + name: last_name + required: false + secret: false + - default: false + description: The user's username. + isArray: false + name: user_name + required: false + secret: false + - default: false + description: The user's email address. Required if email_notice is given. + isArray: false + name: email + required: false + secret: false + - default: false + description: The user's postal address. + isArray: false + name: address + required: false + secret: false + - default: false + description: The user's phone number. + isArray: false + name: phone + required: false + secret: false + - default: false + description: The city the user is living in. + isArray: false + name: city + required: false + secret: false + - default: false + description: The state the user is living in. + isArray: false + name: state + required: false + secret: false + - default: false + description: The country the user is living in. + isArray: false + name: country + required: false + secret: false + - auto: PREDEFINED + default: false + defaultValue: 'false' + description: Default is False. Wether the user should be locked or not. + isArray: false + name: locked + predefined: + - 'true' + - 'false' + required: false + secret: false + - default: false + description: 'The user timezone, possible values can be found here: https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html.' + isArray: false + name: time_zone + required: false + secret: false + - default: false + description: "The user's role. Should be a number between 1 to 7. Role description: 1- Administrator, 2- Security Manager, 3-Security Analyst, 4-Vulnerability Analyst, 5-Executive, 6-Credential Manager, 7-Auditor. Only an Administrator can create Administrator accounts." + isArray: false + name: role_id + required: false + secret: false + auto: PREDEFINED + predefined: + - '0' + - '1' + - '2' + - '3' + - '4' + - '5' + - '6' + - '7' + - default: false + description: When choosing LDAP or SAML auth types, 'must_change_password' must be set to False. For all other cases can be either True or False. + isArray: false + name: must_change_password + required: false + secret: false + auto: PREDEFINED + defaultValue: 'false' + predefined: + - 'false' + - 'true' + - default: false + description: Comma separated list of session user's role can manage group. Use tenable-sc-list-groups to get all available groups. Default is 0. + isArray: false + defaultValue: 0 + name: managed_users_groups + required: false + secret: false + - default: false + description: Comma separated list of session user's role can manage group. Use tenable-sc-list-groups to get all available groups. Default is 0. + isArray: false + defaultValue: 0 + name: managed_objects_groups + required: false + secret: false + - default: false + description: Default is 0. Valid group ID whose users can be managed by created user. + isArray: false + name: group_id + defaultValue: 0 + required: false + secret: false + - default: false + description: Default is 0. ID of a valid, usable, accessible asset. Use tenable-sc-list-assets to get all available assets. -1 is not set, 0 is all assets, and other numbers are asset id. + isArray: false + defaultValue: 0 + name: responsible_asset_id + required: false + secret: false + - default: false + description: The new password to set. Must be given with current_password. Must be at least 3 characters. + isArray: false + name: password + required: false + secret: false + - default: false + description: This is admin/Security Manager password from instance parameters. required when attempting to change user's password. + isArray: false + name: current_password + required: false + secret: false + - default: false + description: The id of the user whose details we wish to update. + isArray: false + name: user_id + required: true + secret: false + deprecated: false + description: update user details by given user_id. + execution: false + name: tenable-sc-update-user + outputs: + + - arguments: + - default: false + description: The id of the user we want to delete. + isArray: false + name: user_id + required: true + secret: false + deprecated: false + description: delete a user by given user_id. + execution: false + name: tenable-sc-delete-user + outputs: + + - arguments: + - default: false + description: The id of the plugin we want to search. If given, other arguments will be ignored. + isArray: false + name: plugin_id + required: false + secret: false + - default: false + defaultValue: '50' + description: Default is 50. The number of objects to return in one response (maximum limit is 200). Ignored when plugin_id is given. + isArray: false + name: limit + required: false + secret: false + - auto: PREDEFINED + default: false + description: default is none. none - both active and passive Plugin Families are returned. true - Only active Plugin Families will be returned. false - Only passive Plugin Families will be returned. Ignored when plugin_id is given. + isArray: false + name: is_active + predefined: + - 'true' + - 'false' + required: false + secret: false + deprecated: false + description: update user details by given user_id. + execution: false + name: tenable-sc-list-plugin-family + outputs: + + - arguments: + - default: false + description: The name of the policy you wish to create. + isArray: false + name: policy_name + required: false + secret: false + - default: false + description: The description of the policy you wish to create. + isArray: false + name: policy_description + required: false + secret: false + - default: false + description: Default is 1. Policy template id. + defaultValue: 1 + isArray: false + name: policy_template_id + required: false + secret: false + - default: false + description: 'Possible values: default, all or a comma separated list of values - 21,23,25,80,110.' + isArray: false + name: port_scan_range + required: false + secret: false + - default: false + description: Only possible if you are using Linux or FreeBSD. On Windows or macOS, the scanner does not do a TCP scan and instead uses the SYN scanner..If you enable this option, you can also set the syn_firewall_detection. + isArray: false + name: tcp_scanner + required: false + secret: false + auto: PREDEFINED + defaultValue: 'no' + predefined: + - 'no' + - 'yes' + - default: false + description: Identifies open TCP ports on the target hosts. If you enable this option, you can also set the syn_firewall_detection option. + isArray: false + name: syn_scanner + required: false + secret: false + auto: PREDEFINED + defaultValue: 'yes' + predefined: + - 'no' + - 'yes' + - default: false + description: Enabling the UDP port scanner may dramatically increase the scan time and produce unreliable results. Consider using the netstat or SNMP port enumeration options instead if possible. + isArray: false + name: udp_scanner + required: false + secret: false + auto: PREDEFINED + defaultValue: 'no' + predefined: + - 'no' + - 'yes' + - default: false + description: Can be retrieved from the result of tenable-sc-list-plugin-family command . + isArray: false + name: family_id + required: true + secret: false + - default: false + description: Comma separated list of plugin_ids, Can be retrieved from the result of tenable-sc-list-plugin-family command with family_id as argument. + isArray: false + name: plugins_id + required: false + secret: false + - default: false + description: Default is Automatic (normal). Rely on local port enumeration first before relying on network port scans. + isArray: false + name: syn_firewall_detection + required: false + secret: false + auto: PREDEFINED + defaultValue: Automatic (normal) + predefined: + - Automatic (normal) + - Do not detect RST rate limitation(soft) + - Ignore closed ports(aggressive) + - Disabled(softer) + deprecated: false + description: This command is prerequisite for creating remediation scan. creates policy. + execution: false + name: tenable-sc-create-policy + outputs: + + + - arguments: + - default: false + defaultValue: '50' + description: Default is 50. The number of objects to return in one response (maximum limit is 200). + isArray: false + name: limit + required: false + secret: false + - default: false + description: The id of the query we wish to search. + isArray: false + name: query_id + required: false + secret: false + - default: false + description: The query time to retrieve. When no type is set all queries are returned. + isArray: false + name: type + required: false + secret: false + auto: PREDEFINED + predefined: + - alert + - lce + - mobile + - ticket + - user + deprecated: false + description: Lists queries. + execution: false + name: tenable-sc-list-query + outputs: + isfetch: true runonce: false script: '-' From 6e63b53d4004377068af366d2be102a2dde201c5 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Mon, 15 May 2023 17:14:54 +0300 Subject: [PATCH 020/115] finish create-user command --- .../Integrations/Tenable_sc/Tenable_sc.py | 215 ++++++++++------- .../Integrations/Tenable_sc/Tenable_sc.yml | 224 ++++++++++++++++-- 2 files changed, 330 insertions(+), 109 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 92d7cb59e2d0..1b79a9b1b44a 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -541,50 +541,6 @@ def create_policy(self, policy_name, policy_description, policy_template_id, por } return self.send_request(path="policy", method='POST', body=body) - # def update_user(self, first_name, last_name, user_name, email, address, phone, city, state, country, locked, - # time_zone, must_change_password, role_id, managed_users_groups, managed_objects_groups, - # group_id, responsible_asset_id, user_id, current_password): - # body["preferences"] = [] - - # if first_name: - # body["firstname"] = first_name - # if last_name: - # body["lastname"] = last_name - # if user_name: - # body["username"] = user_name - # if email: - # body["email"] = email - # if city: - # body["city"] = city - # if state: - # body["state"] = state - # if address: - # body["address"] = address - # if country: - # body["country"] = country - # if role_id: - # body["roleID"] = role_id - # if phone: - # body["phone"] = phone - # if locked: - # body["locked"] = locked - # if time_zone: - # body["preferences"].append({"name": "timezone", "value": time_zone, "tag": ""}) - # if must_change_password: - # body["mustChangePassword"] = must_change_password - # if current_password: - # body[""] - - # if role_id and role_id != 1: - # body["groupID"] = group_id - # body["responsibleAssetID"] = responsible_asset_id - # elif role_id and role_id == 1: - # body["managedUsersGroups"] = [{"id": int(managed_users_group)} for managed_users_group in managed_users_groups] - # body["managedObjectsGroups"] = [{"id": int(managed_objects_group)} for - # managed_objects_group in managed_objects_groups] - - # return self.send_request(path='user', body=body, method='PATCH') - ''' HELPER FUNCTIONS ''' @@ -612,19 +568,19 @@ def create_user_request_body(args): "phone": "phone", "locked": "locked", "mustChangePassword": "must_change_password", - "currentPassword": "current_password" + "currentPassword": "current_password", + "password": "password", + "groupID": "group_id", + "responsibleAssetID": "responsible_asset_id" } body = {key: args.get(value) for key, value in user_query_mapping_dict.items() if args.get(value)} - if role_id := args.get('role_id'): - if role_id != 1: - body["groupID"] = args.get('group_id') - body["responsibleAssetID"] = args.get('responsible_asset_id') - else: - body["managedUsersGroups"] = [{"id": int(managed_users_group)} for - managed_users_group in args.get('managed_users_groups')] - body["managedObjectsGroups"] = [{"id": int(managed_objects_group)} for - managed_objects_group in args.get('managed_objects_groups')] + if args.get('managed_users_groups'): + body["managedUsersGroups"] = [{"id": managed_users_group} for + managed_users_group in args.get('managed_users_groups').split(',')] + if args.get('managed_objects_groups'): + body["managedObjectsGroups"] = [{"id": int(managed_objects_group)} for + managed_objects_group in args.get('managed_objects_groups').split(',')] if time_zone := args.get('time_zone'): body["preferences"].append([{"name": "timezone", "value": time_zone, "tag": ""}]) @@ -673,8 +629,8 @@ def validate_user_body_params(args, command_type): if email and not re.compile(emailRegex).match(email): return_error(f"Error: The given email address: {email} is not valid") - if command_type == 'create' and not email_notice == 'None' and not email: - return_error("When email_notice is different from None, an email must be given as well.") + if command_type == 'create' and not email_notice == 'none' and not email: + return_error("When email_notice is different from none, an email must be given as well.") def timestamp_to_utc(timestamp_str, default_returned_value=''): @@ -1153,17 +1109,7 @@ def create_scan_command(client: Client, args: Dict[str, Any]): def launch_scan_command(client: Client, args: Dict[str, Any]): - scan_id = args.get('scan_id') - target_address = args.get('diagnostic_target') - target_password = args.get('diagnostic_password') - - if (target_address and not target_password) or (target_password and not target_address): - return_error('Error: If a target is provided, both IP/Hostname and the password must be provided') - - res = client.launch_scan(scan_id, {'address': target_address, 'password': target_password}) - - if not res or 'response' not in res or not res['response'] or 'scanResult' not in res['response']: - return_error('Error: Could not retrieve the scan') + res = launch_scan(client, args) scan_result = res['response']['scanResult'] @@ -1195,16 +1141,42 @@ def launch_scan_command(client: Client, args: Dict[str, Any]): }) -def get_scan_status_command(client: Client, args: Dict[str, Any]): - scan_results_ids = argToList(args.get('scan_results_id')) +def launch_scan_report_command(client: Client, args: Dict[str, Any]): + res = launch_scan(client, args) + scan_id = res.get("response", {}).get("scanID") + poll_scan_status(client, {"scan_results_id": scan_id}) - scans_results = [] - for scan_results_id in scan_results_ids: - res = client.get_scan_results(scan_results_id) - if not res or 'response' not in res or not res['response']: - return_message('Scan results not found') - scans_results.append(res['response']) +@polling_function('tenable-sc-poll-scan-status') +def poll_scan_status(client: Client, args: Dict[str, Any]): + scan_results, _ = get_scan_status(client, args)[0] + is_scan_incomplete = scan_results.get("status") != "Completed" + if is_scan_incomplete: + return PollResult(continue_to_poll=True, response=scan_results) + else: + return PollResult(continue_to_poll=False, response=process_results(scan_results)) + + +def process_results(scan_results): + id = scan_results.get("id") + + +def launch_scan(client: Client, args: Dict[str, Any]): + scan_id = args.get('scan_id') + target_address = args.get('diagnostic_target') + target_password = args.get('diagnostic_password') + + if (target_address and not target_password) or (target_password and not target_address): + return_error('Error: If a target is provided, both IP/Hostname and the password must be provided') + + res = client.launch_scan(scan_id, {'address': target_address, 'password': target_password}) + + if not res or 'response' not in res or not res['response'] or 'scanResult' not in res['response']: + return_error('Error: Could not retrieve the scan') + + +def get_scan_status_command(client: Client, args: Dict[str, Any]): + scans_results, res = get_scan_status(client, args) headers = ['ID', 'Name', 'Status', 'Description'] @@ -1227,6 +1199,19 @@ def get_scan_status_command(client: Client, args: Dict[str, Any]): }) +def get_scan_status(client: Client, args: Dict[str, Any]): + scan_results_ids = argToList(args.get('scan_results_id')) + + scans_results = [] + for scan_results_id in scan_results_ids: + res = client.get_scan_results(scan_results_id) + if not res or 'response' not in res or not res['response']: + return_message('Scan results not found') + + scans_results.append(res['response']) + return scans_results, res + + def get_scan_report_command(client: Client, args: Dict[str, Any]): scan_results_id = args.get('scan_results_id') vulnerabilities_to_get = argToList(args.get('vulnerability_severity', [])) @@ -1885,11 +1870,11 @@ def list_groups_command(client: Client, args: Dict[str, Any]): show_users = argToBoolean(args.get("show_users", True)) limit = int(args.get('limit', '50')) res = client.list_groups(show_users) - if len(res) > limit: - res = res[:limit] if not res or not res.get('response', []): return_message('No groups found') groups = res.get('response', []) + if len(groups) > limit: + groups = groups[:limit] mapped_groups = [{ 'ID': group.get('id'), 'Name': group.get('name'), @@ -1976,18 +1961,71 @@ def get_all_scan_results_command(client: Client, args: Dict[str, Any]): def create_user_command(client: Client, args: Dict[str, Any]): validate_user_body_params(args, "create") res = client.create_user(args) - print(res) + if not res or not res.get('response', {}): + return_message("User wasn't created successfully.") + headers = ["User type", "User Id", "User Status", "User Name", "First Name", "Lat Name ", "Email ", "User Role Name", + "User Group Name", "User LDAP Name"] + response = res.get("response", {}) + mapped_response = { + "User type": res.get("type"), + "User Id": response.get("id"), + "User Status": response.get("status"), + "User Name": response.get("username"), + "First Name": response.get("firstname"), + "Lat Name ": response.get("lastname"), + "Email ": response.get("email"), + "User Role Name": response.get("role", {}).get("name"), + "User Group Name": response.get("group", {}).get("name"), + "User LDAP Name": response.get("ldap", {}).get("name") + } + + demisto.results({ + 'Type': entryTypes['note'], + 'Contents': response, + 'ContentsFormat': formats['json'], + 'ReadableContentsFormat': formats['markdown'], + 'HumanReadable': tableToMarkdown(f'User {args.get("user_name")} was created successfully.', mapped_response, + headers, removeNull=True), + 'EntryContext': { + 'TenableSC.User(val.ID===obj.ID)': createContext(response, removeNull=True) + } + }) def update_user_command(client: Client, args: Dict[str, Any]): - # must_change_password = argToBoolean(args.get('must_change_password', True)) - # locked = argToBoolean(args.get('locked', False)) - # managed_users_groups = args.get('managed_users_groups', '0').split(',') - # managed_objects_groups = args.get('managed_objects_groups', '0').split(',') user_id = args.get('user_id') validate_user_body_params(args, "update") res = client.update_user(args, user_id) print(res) + # if not res or not res.get('response', {}): + # return_message("User wasn't created successfully.") + # headers = ["User type", "User Id", "User Status", "User Name", "First Name", "Lat Name ", "Email ", "User Role Name", + # "User Group Name", "User LDAP Name"] + # response = res.get("response", {}) + # mapped_response = { + # "User type": res.get("type"), + # "User Id": response.get("id"), + # "User Status": response.get("status"), + # "User Name": response.get("username"), + # "First Name": response.get("firstname"), + # "Lat Name ": response.get("lastname"), + # "Email ": response.get("email"), + # "User Role Name": response.get("role", {}).get("name"), + # "User Group Name": response.get("group", {}).get("name"), + # "User LDAP Name": response.get("ldap", {}).get("name") + # } + + # demisto.results({ + # 'Type': entryTypes['note'], + # 'Contents': res, + # 'ContentsFormat': formats['json'], + # 'ReadableContentsFormat': formats['markdown'], + # 'HumanReadable': tableToMarkdown(f'user {args.get("user_name")} was created succesfully.', mapped_response, + # headers, removeNull=True), + # 'EntryContext': { + # 'TenableSC.User(val.ID===obj.ID)': createContext(response, removeNull=True) + # } + # }) def delete_user_command(client: Client, args: Dict[str, Any]): @@ -2044,22 +2082,22 @@ def create_policy_command(client: Client, args: Dict[str, Any]): udp_scanner, syn_firewall_detection, family_id, plugins_id) created_policy = res.get("response") mapped_created_policy = { - # "Policy type": created_policy.get(""), + "Policy type": res.get("type"), "Policy ID": created_policy.get("id"), "name": created_policy.get("name"), "Description": created_policy.get("description"), "Created Time": created_policy.get("createdTime"), - # "Plugin Families": created_policy.get(""), + "Plugin Families": created_policy.get("families"), "Policy Status": created_policy.get("status"), "Policy UUID": created_policy.get("uuid"), "Policy can Manage": created_policy.get("canManage"), "Creator Username": created_policy.get("creator", {}).get("username"), - # "Owner Username": created_policy.get(""), + "Owner ID": created_policy.get("ownerID"), "policyTemplate ID": created_policy.get("policyTemplate", {}).get("id"), "policyTemplate Name": created_policy.get("policyTemplate", {}).get("name") } headers = ["Policy type", "Policy Id", "name", "Description", "Created Time", "Plugin Families", "Policy Status", - "Policy UUID", "Policy can Manage", "Creator Username", "Owner Username", "policyTemplate id", + "Policy UUID", "Policy can Manage", "Creator Username", "Owner ID", "policyTemplate id", "policyTemplate Name"] demisto.results({ 'Type': entryTypes['note'], @@ -2192,12 +2230,15 @@ def main(): 'tenable-sc-get-system-licensing': get_system_licensing_command, 'tenable-sc-get-all-scan-results': get_all_scan_results_command, 'tenable-sc-list-groups': list_groups_command, + 'tenable-sc-create-user': create_user_command, 'tenable-sc-update-user': update_user_command, 'tenable-sc-delete-user': delete_user_command, 'tenable-sc-list-plugin-family': list_plugin_family_command, 'tenable-sc-create-policy': create_policy_command, - 'tenable-sc-list-query': list_query_command + 'tenable-sc-list-query': list_query_command, + 'tenable-sc-launch-scan-report': launch_scan_report_command, + 'tenable-sc-poll-scan-status': poll_scan_status } try: diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index 40a66ee06d60..d57fae844257 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -1247,7 +1247,7 @@ script: required: false secret: false deprecated: false - description: list all groups. + description: Available only for sec man. list all groups. execution: false name: tenable-sc-list-groups outputs: @@ -1275,7 +1275,6 @@ script: - contextPath: TenableSC.Group.Users.Username description: Group's user's user name. type: string - - arguments: - default: false description: The user's first name. @@ -1349,12 +1348,12 @@ script: required: false secret: false auto: PREDEFINED - defaultValue: 'None' + defaultValue: 'none' predefined: - 'both' - 'password' - 'id' - - 'None' + - 'none' - default: false description: Tenable (TNS). Lightweight Directory Access Protocol (LDAP). Security Assertion Markup Language (SAML). LDAP server or SAML authentication need to be configured in order to select LDAP or SAML. isArray: false @@ -1441,6 +1440,194 @@ script: execution: false name: tenable-sc-create-user outputs: + - contextPath: TenableSC.User.address + description: '' + type: String + - contextPath: TenableSC.User.apiKeys + description: '' + type: Unknown + - contextPath: TenableSC.User.authType + description: '' + type: String + - contextPath: TenableSC.User.canManage + description: '' + type: Boolean + - contextPath: TenableSC.User.canUse + description: '' + type: Boolean + - contextPath: TenableSC.User.city + description: '' + type: String + - contextPath: TenableSC.User.country + description: '' + type: String + - contextPath: TenableSC.User.createdTime + description: '' + type: Date + - contextPath: TenableSC.User.email + description: '' + type: String + - contextPath: TenableSC.User.failedLogins + description: '' + type: String + - contextPath: TenableSC.User.fax + description: '' + type: String + - contextPath: TenableSC.User.fingerprint + description: '' + type: Unknown + - contextPath: TenableSC.User.firstname + description: '' + type: String + - contextPath: TenableSC.User.group.description + description: '' + type: String + - contextPath: TenableSC.User.group.id + description: '' + type: String + - contextPath: TenableSC.User.group.name + description: '' + type: String + - contextPath: TenableSC.User.id + description: '' + type: String + - contextPath: TenableSC.User.lastLogin + description: '' + type: String + - contextPath: TenableSC.User.lastLoginIP + description: '' + type: String + - contextPath: TenableSC.User.lastname + description: '' + type: String + - contextPath: TenableSC.User.ldap.description + description: '' + type: String + - contextPath: TenableSC.User.ldap.id + description: '' + type: Number + - contextPath: TenableSC.User.ldap.name + description: '' + type: String + - contextPath: TenableSC.User.ldapUsername + description: '' + type: String + - contextPath: TenableSC.User.locked + description: '' + type: String + - contextPath: TenableSC.User.managedObjectsGroups.description + description: '' + type: String + - contextPath: TenableSC.User.managedObjectsGroups.id + description: '' + type: String + - contextPath: TenableSC.User.managedObjectsGroups.name + description: '' + type: String + - contextPath: TenableSC.User.managedUsersGroups.description + description: '' + type: String + - contextPath: TenableSC.User.managedUsersGroups.id + description: '' + type: String + - contextPath: TenableSC.User.managedUsersGroups.name + description: '' + type: String + - contextPath: TenableSC.User.modifiedTime + description: '' + type: Date + - contextPath: TenableSC.User.mustChangePassword + description: '' + type: String + - contextPath: TenableSC.User.password + description: '' + type: String + - contextPath: TenableSC.User.phone + description: '' + type: String + - contextPath: TenableSC.User.preferences.name + description: '' + type: String + - contextPath: TenableSC.User.preferences.tag + description: '' + type: String + - contextPath: TenableSC.User.preferences.value + description: '' + type: String + - contextPath: TenableSC.User.responsibleAsset.description + description: '' + type: String + - contextPath: TenableSC.User.responsibleAsset.id + description: '' + type: String + - contextPath: TenableSC.User.responsibleAsset.name + description: '' + type: String + - contextPath: TenableSC.User.responsibleAsset.uuid + description: '' + type: Unknown + - contextPath: TenableSC.User.role.description + description: '' + type: String + - contextPath: TenableSC.User.role.id + description: '' + type: String + - contextPath: TenableSC.User.role.name + description: '' + type: String + - contextPath: TenableSC.User.state + description: '' + type: String + - contextPath: TenableSC.User.status + description: '' + type: String + - contextPath: TenableSC.User.title + description: '' + type: String + - contextPath: TenableSC.User.username + description: '' + type: String + - contextPath: TenableSC.User.uuid + description: '' + type: String + + + + - arguments: + - default: false + defaultValue: '50' + description: Default is 50. The number of objects to return in one response (maximum limit is 200). + isArray: false + name: limit + required: false + secret: false + - default: false + description: The id of the query we wish to search. + isArray: false + name: query_id + required: false + secret: false + - default: false + description: The query time to retrieve. When no type is set all queries are returned. + isArray: false + name: type + required: false + secret: false + auto: PREDEFINED + predefined: + - alert + - lce + - mobile + - ticket + - user + deprecated: false + description: Lists queries. + execution: false + name: tenable-sc-list-query + outputs: + + + - arguments: - default: false @@ -1725,41 +1912,34 @@ script: execution: false name: tenable-sc-create-policy outputs: - + - arguments: - default: false - defaultValue: '50' - description: Default is 50. The number of objects to return in one response (maximum limit is 200). + description: The id of the scan we wish to get the report on. Can be retrieved from list-scans command. isArray: false - name: limit - required: false + name: scan_id + required: true secret: false - default: false - description: The id of the query we wish to search. + description: Valid IP/Hostname of a specific target to scan. Must be provided with diagnostic_password. isArray: false - name: query_id + name: diagnostic_target required: false secret: false - default: false - description: The query time to retrieve. When no type is set all queries are returned. + description: Valid password of the diagnostic_target. Must be provided with diagnostic_target. isArray: false - name: type + name: diagnostic_password required: false secret: false - auto: PREDEFINED - predefined: - - alert - - lce - - mobile - - ticket - - user deprecated: false description: Lists queries. execution: false - name: tenable-sc-list-query + name: tenable-sc-launch-scan-report outputs: - + + isfetch: true runonce: false script: '-' From 811418f6282dec4bd162bc4bc40a5f9260534f15 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Tue, 16 May 2023 09:35:24 +0300 Subject: [PATCH 021/115] finish update-user command --- .../Integrations/Tenable_sc/Tenable_sc.py | 55 +-- .../Integrations/Tenable_sc/Tenable_sc.yml | 326 +++++++++++++----- 2 files changed, 252 insertions(+), 129 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 1b79a9b1b44a..de9bf1abfafa 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -1961,6 +1961,19 @@ def get_all_scan_results_command(client: Client, args: Dict[str, Any]): def create_user_command(client: Client, args: Dict[str, Any]): validate_user_body_params(args, "create") res = client.create_user(args) + hr_header = f'User {args.get("user_name")} was created successfully.' + process_update_and_create_user_response(res, hr_header) + + +def update_user_command(client: Client, args: Dict[str, Any]): + user_id = args.get('user_id') + validate_user_body_params(args, "update") + res = client.update_user(args, user_id) + hr_header = f'user {args.get("user_id")} was updated succesfully.' + process_update_and_create_user_response(res, hr_header) + + +def process_update_and_create_user_response(res, hr_header): if not res or not res.get('response', {}): return_message("User wasn't created successfully.") headers = ["User type", "User Id", "User Status", "User Name", "First Name", "Lat Name ", "Email ", "User Role Name", @@ -1978,56 +1991,18 @@ def create_user_command(client: Client, args: Dict[str, Any]): "User Group Name": response.get("group", {}).get("name"), "User LDAP Name": response.get("ldap", {}).get("name") } - demisto.results({ 'Type': entryTypes['note'], 'Contents': response, 'ContentsFormat': formats['json'], 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown(f'User {args.get("user_name")} was created successfully.', mapped_response, - headers, removeNull=True), + 'HumanReadable': tableToMarkdown(hr_header, mapped_response, headers, removeNull=True), 'EntryContext': { 'TenableSC.User(val.ID===obj.ID)': createContext(response, removeNull=True) } }) -def update_user_command(client: Client, args: Dict[str, Any]): - user_id = args.get('user_id') - validate_user_body_params(args, "update") - res = client.update_user(args, user_id) - print(res) - # if not res or not res.get('response', {}): - # return_message("User wasn't created successfully.") - # headers = ["User type", "User Id", "User Status", "User Name", "First Name", "Lat Name ", "Email ", "User Role Name", - # "User Group Name", "User LDAP Name"] - # response = res.get("response", {}) - # mapped_response = { - # "User type": res.get("type"), - # "User Id": response.get("id"), - # "User Status": response.get("status"), - # "User Name": response.get("username"), - # "First Name": response.get("firstname"), - # "Lat Name ": response.get("lastname"), - # "Email ": response.get("email"), - # "User Role Name": response.get("role", {}).get("name"), - # "User Group Name": response.get("group", {}).get("name"), - # "User LDAP Name": response.get("ldap", {}).get("name") - # } - - # demisto.results({ - # 'Type': entryTypes['note'], - # 'Contents': res, - # 'ContentsFormat': formats['json'], - # 'ReadableContentsFormat': formats['markdown'], - # 'HumanReadable': tableToMarkdown(f'user {args.get("user_name")} was created succesfully.', mapped_response, - # headers, removeNull=True), - # 'EntryContext': { - # 'TenableSC.User(val.ID===obj.ID)': createContext(response, removeNull=True) - # } - # }) - - def delete_user_command(client: Client, args: Dict[str, Any]): user_id = args.get('user_id') client.delete_user(user_id) @@ -2230,9 +2205,9 @@ def main(): 'tenable-sc-get-system-licensing': get_system_licensing_command, 'tenable-sc-get-all-scan-results': get_all_scan_results_command, 'tenable-sc-list-groups': list_groups_command, - 'tenable-sc-create-user': create_user_command, 'tenable-sc-update-user': update_user_command, + 'tenable-sc-delete-user': delete_user_command, 'tenable-sc-list-plugin-family': list_plugin_family_command, 'tenable-sc-create-policy': create_policy_command, diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index d57fae844257..91dd960900a0 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -1441,194 +1441,155 @@ script: name: tenable-sc-create-user outputs: - contextPath: TenableSC.User.address - description: '' + description: User address. type: String - contextPath: TenableSC.User.apiKeys - description: '' + description: User api keys. type: Unknown - contextPath: TenableSC.User.authType - description: '' + description: User auth type. type: String - contextPath: TenableSC.User.canManage - description: '' + description: User permissions. type: Boolean - contextPath: TenableSC.User.canUse - description: '' + description: User permissions. type: Boolean - contextPath: TenableSC.User.city - description: '' + description: User city of residence. type: String - contextPath: TenableSC.User.country - description: '' + description: User country of residence. type: String - contextPath: TenableSC.User.createdTime - description: '' + description: User creation time. type: Date - contextPath: TenableSC.User.email - description: '' + description: User email address. type: String - contextPath: TenableSC.User.failedLogins - description: '' + description: User number of failed logins. type: String - contextPath: TenableSC.User.fax - description: '' + description: User fax. type: String - contextPath: TenableSC.User.fingerprint - description: '' + description: User fingerprint. type: Unknown - contextPath: TenableSC.User.firstname - description: '' + description: User first name. type: String - contextPath: TenableSC.User.group.description - description: '' + description: User group's description. type: String - contextPath: TenableSC.User.group.id - description: '' + description: User group's id. type: String - contextPath: TenableSC.User.group.name - description: '' + description: User group's name. type: String - contextPath: TenableSC.User.id - description: '' + description: User id. type: String - contextPath: TenableSC.User.lastLogin - description: '' + description: User last login time. type: String - contextPath: TenableSC.User.lastLoginIP - description: '' + description: User last login IP. type: String - contextPath: TenableSC.User.lastname - description: '' + description: User last name. type: String - contextPath: TenableSC.User.ldap.description - description: '' + description: User ldap description. type: String - contextPath: TenableSC.User.ldap.id - description: '' + description: User ldap ID> type: Number - contextPath: TenableSC.User.ldap.name - description: '' + description: User ldap name. type: String - contextPath: TenableSC.User.ldapUsername - description: '' + description: user ldap username. type: String - contextPath: TenableSC.User.locked - description: '' + description: if user is locked or not. type: String - contextPath: TenableSC.User.managedObjectsGroups.description - description: '' + description: User managed object groups description. type: String - contextPath: TenableSC.User.managedObjectsGroups.id - description: '' + description: User managed object groups id type: String - contextPath: TenableSC.User.managedObjectsGroups.name - description: '' + description: User managed object groups name. type: String - contextPath: TenableSC.User.managedUsersGroups.description - description: '' + description: User managed users groups description. type: String - contextPath: TenableSC.User.managedUsersGroups.id - description: '' + description: User managed users groups id. type: String - contextPath: TenableSC.User.managedUsersGroups.name - description: '' + description: User managed users groups name. type: String - contextPath: TenableSC.User.modifiedTime - description: '' + description: User last modification time. type: Date - contextPath: TenableSC.User.mustChangePassword - description: '' + description: If user must change password. type: String - contextPath: TenableSC.User.password - description: '' + description: If user password is set. type: String - contextPath: TenableSC.User.phone - description: '' + description: User phone number. type: String - contextPath: TenableSC.User.preferences.name - description: '' + description: User preferences name. type: String - contextPath: TenableSC.User.preferences.tag - description: '' + description: User preferences tag. type: String - contextPath: TenableSC.User.preferences.value - description: '' + description: User preferences value. type: String - contextPath: TenableSC.User.responsibleAsset.description - description: '' + description: User responsible asset description. type: String - contextPath: TenableSC.User.responsibleAsset.id - description: '' + description: User responsible asset id. type: String - contextPath: TenableSC.User.responsibleAsset.name - description: '' + description: User responsible asset name. type: String - contextPath: TenableSC.User.responsibleAsset.uuid - description: '' + description: User responsible asset uuid. type: Unknown - contextPath: TenableSC.User.role.description - description: '' + description: User tole description. type: String - contextPath: TenableSC.User.role.id - description: '' + description: User role id. type: String - contextPath: TenableSC.User.role.name - description: '' + description: User role name type: String - contextPath: TenableSC.User.state - description: '' + description: User state. type: String - contextPath: TenableSC.User.status - description: '' + description: User status. type: String - contextPath: TenableSC.User.title - description: '' + description: User title. type: String - contextPath: TenableSC.User.username - description: '' + description: User username. type: String - contextPath: TenableSC.User.uuid - description: '' + description: User UUID. type: String - - - - - arguments: - - default: false - defaultValue: '50' - description: Default is 50. The number of objects to return in one response (maximum limit is 200). - isArray: false - name: limit - required: false - secret: false - - default: false - description: The id of the query we wish to search. - isArray: false - name: query_id - required: false - secret: false - - default: false - description: The query time to retrieve. When no type is set all queries are returned. - isArray: false - name: type - required: false - secret: false - auto: PREDEFINED - predefined: - - alert - - lce - - mobile - - ticket - - user - deprecated: false - description: Lists queries. - execution: false - name: tenable-sc-list-query - outputs: - - - - - arguments: - default: false description: The user's first name. @@ -1779,6 +1740,193 @@ script: execution: false name: tenable-sc-update-user outputs: + - contextPath: TenableSC.User.address + description: User address. + type: String + - contextPath: TenableSC.User.apiKeys + description: User api keys. + type: Unknown + - contextPath: TenableSC.User.authType + description: User auth type. + type: String + - contextPath: TenableSC.User.canManage + description: User permissions. + type: Boolean + - contextPath: TenableSC.User.canUse + description: User permissions. + type: Boolean + - contextPath: TenableSC.User.city + description: User city of residence. + type: String + - contextPath: TenableSC.User.country + description: User country of residence. + type: String + - contextPath: TenableSC.User.createdTime + description: User creation time. + type: Date + - contextPath: TenableSC.User.email + description: User email address. + type: String + - contextPath: TenableSC.User.failedLogins + description: User number of failed logins. + type: String + - contextPath: TenableSC.User.fax + description: User fax. + type: String + - contextPath: TenableSC.User.fingerprint + description: User fingerprint. + type: Unknown + - contextPath: TenableSC.User.firstname + description: User first name. + type: String + - contextPath: TenableSC.User.group.description + description: User group's description. + type: String + - contextPath: TenableSC.User.group.id + description: User group's id. + type: String + - contextPath: TenableSC.User.group.name + description: User group's name. + type: String + - contextPath: TenableSC.User.id + description: User id. + type: String + - contextPath: TenableSC.User.lastLogin + description: User last login time. + type: String + - contextPath: TenableSC.User.lastLoginIP + description: User last login IP. + type: String + - contextPath: TenableSC.User.lastname + description: User last name. + type: String + - contextPath: TenableSC.User.ldap.description + description: User ldap description. + type: String + - contextPath: TenableSC.User.ldap.id + description: User ldap ID> + type: Number + - contextPath: TenableSC.User.ldap.name + description: User ldap name. + type: String + - contextPath: TenableSC.User.ldapUsername + description: user ldap username. + type: String + - contextPath: TenableSC.User.locked + description: if user is locked or not. + type: String + - contextPath: TenableSC.User.managedObjectsGroups.description + description: User managed object groups description. + type: String + - contextPath: TenableSC.User.managedObjectsGroups.id + description: User managed object groups id + type: String + - contextPath: TenableSC.User.managedObjectsGroups.name + description: User managed object groups name. + type: String + - contextPath: TenableSC.User.managedUsersGroups.description + description: User managed users groups description. + type: String + - contextPath: TenableSC.User.managedUsersGroups.id + description: User managed users groups id. + type: String + - contextPath: TenableSC.User.managedUsersGroups.name + description: User managed users groups name. + type: String + - contextPath: TenableSC.User.modifiedTime + description: User last modification time. + type: Date + - contextPath: TenableSC.User.mustChangePassword + description: If user must change password. + type: String + - contextPath: TenableSC.User.password + description: If user password is set. + type: String + - contextPath: TenableSC.User.phone + description: User phone number. + type: String + - contextPath: TenableSC.User.preferences.name + description: User preferences name. + type: String + - contextPath: TenableSC.User.preferences.tag + description: User preferences tag. + type: String + - contextPath: TenableSC.User.preferences.value + description: User preferences value. + type: String + - contextPath: TenableSC.User.responsibleAsset.description + description: User responsible asset description. + type: String + - contextPath: TenableSC.User.responsibleAsset.id + description: User responsible asset id. + type: String + - contextPath: TenableSC.User.responsibleAsset.name + description: User responsible asset name. + type: String + - contextPath: TenableSC.User.responsibleAsset.uuid + description: User responsible asset uuid. + type: Unknown + - contextPath: TenableSC.User.role.description + description: User tole description. + type: String + - contextPath: TenableSC.User.role.id + description: User role id. + type: String + - contextPath: TenableSC.User.role.name + description: User role name + type: String + - contextPath: TenableSC.User.state + description: User state. + type: String + - contextPath: TenableSC.User.status + description: User status. + type: String + - contextPath: TenableSC.User.title + description: User title. + type: String + - contextPath: TenableSC.User.username + description: User username. + type: String + - contextPath: TenableSC.User.uuid + description: User UUID. + type: String + + + + - arguments: + - default: false + defaultValue: '50' + description: Default is 50. The number of objects to return in one response (maximum limit is 200). + isArray: false + name: limit + required: false + secret: false + - default: false + description: The id of the query we wish to search. + isArray: false + name: query_id + required: false + secret: false + - default: false + description: The query time to retrieve. When no type is set all queries are returned. + isArray: false + name: type + required: false + secret: false + auto: PREDEFINED + predefined: + - alert + - lce + - mobile + - ticket + - user + deprecated: false + description: Lists queries. + execution: false + name: tenable-sc-list-query + outputs: + + - arguments: - default: false From 6abf3cd0bbff72da11f05f58e3c0ac13f53a0047 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Tue, 16 May 2023 15:29:01 +0300 Subject: [PATCH 022/115] in progress --- .../Integrations/Tenable_sc/Tenable_sc.py | 41 +- .../Integrations/Tenable_sc/Tenable_sc.yml | 764 ++++++++++++++---- 2 files changed, 641 insertions(+), 164 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index de9bf1abfafa..433844c1bc37 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -536,7 +536,7 @@ def create_policy(self, policy_name, policy_description, policy_template_id, por "syn_firewall_detection": syn_firewall_detection }, "policyTemplate": { - "policy_template_id": policy_template_id + "id": policy_template_id }, } return self.send_request(path="policy", method='POST', body=body) @@ -1143,22 +1143,18 @@ def launch_scan_command(client: Client, args: Dict[str, Any]): def launch_scan_report_command(client: Client, args: Dict[str, Any]): res = launch_scan(client, args) - scan_id = res.get("response", {}).get("scanID") + scan_id = res.get("response", {}).get("scanResult", {}).get("id") poll_scan_status(client, {"scan_results_id": scan_id}) @polling_function('tenable-sc-poll-scan-status') def poll_scan_status(client: Client, args: Dict[str, Any]): - scan_results, _ = get_scan_status(client, args)[0] - is_scan_incomplete = scan_results.get("status") != "Completed" + scan_results, _ = get_scan_status(client, args) + is_scan_incomplete = scan_results[0].get("status") != "Completed" if is_scan_incomplete: return PollResult(continue_to_poll=True, response=scan_results) else: - return PollResult(continue_to_poll=False, response=process_results(scan_results)) - - -def process_results(scan_results): - id = scan_results.get("id") + return PollResult(get_scan_report_command(client, {"scan_results_id": scan_results})) def launch_scan(client: Client, args: Dict[str, Any]): @@ -1174,6 +1170,8 @@ def launch_scan(client: Client, args: Dict[str, Any]): if not res or 'response' not in res or not res['response'] or 'scanResult' not in res['response']: return_error('Error: Could not retrieve the scan') + return res + def get_scan_status_command(client: Client, args: Dict[str, Any]): scans_results, res = get_scan_status(client, args) @@ -1224,10 +1222,12 @@ def get_scan_report_command(client: Client, args: Dict[str, Any]): scan_results = res['response'] headers = ['ID', 'Name', 'Description', 'Policy', 'Group', 'Owner', 'ScannedIPs', - 'StartTime', 'EndTime', 'Duration', 'Checks', 'ImportTime', 'RepositoryName', 'Status'] + 'StartTime', 'EndTime', 'Duration', 'Checks', 'ImportTime', 'RepositoryName', 'Status', + 'Scan Type', 'Import Status', 'Is Scan Running', 'Completed IPs'] vuln_headers = ['ID', 'Name', 'Family', 'Severity', 'Total'] mapped_results = { + 'Scan Type': res['type'], 'ID': scan_results['id'], 'Name': scan_results['name'], 'Status': scan_results['status'], @@ -1241,7 +1241,10 @@ def get_scan_report_command(client: Client, args: Dict[str, Any]): 'ImportTime': timestamp_to_utc(scan_results['importStart']), 'ScannedIPs': scan_results['scannedIPs'], 'Owner': scan_results['owner'].get('username'), - 'RepositoryName': scan_results['repository'].get('name') + 'RepositoryName': scan_results['repository'].get('name'), + 'Import Status': scan_results['importStatus'], + 'Is Scan Running ': scan_results['running'], + 'Completed IPs': scan_results['progress']['completedIPs'] } hr = tableToMarkdown('Tenable.sc Scan ' + mapped_results['ID'] + ' Report', @@ -1993,7 +1996,7 @@ def process_update_and_create_user_response(res, hr_header): } demisto.results({ 'Type': entryTypes['note'], - 'Contents': response, + 'Contents': res, 'ContentsFormat': formats['json'], 'ReadableContentsFormat': formats['markdown'], 'HumanReadable': tableToMarkdown(hr_header, mapped_response, headers, removeNull=True), @@ -2005,8 +2008,12 @@ def process_update_and_create_user_response(res, hr_header): def delete_user_command(client: Client, args: Dict[str, Any]): user_id = args.get('user_id') - client.delete_user(user_id) - return demisto.results({ + res = client.delete_user(user_id) + demisto.results({ + 'Type': entryTypes['note'], + 'Contents': res, + 'ContentsFormat': formats['json'], + 'ReadableContentsFormat': formats['text'], 'HumanReadable': f"User {user_id} is deleted." }) @@ -2020,8 +2027,8 @@ def list_plugin_family_command(client: Client, args: Dict[str, Any]): return_message('No plugins found') plugins = res.get('response') if isinstance(plugins, dict): - plugins = [plugins] is_active = "false" if plugins.get("type") == "passive" else "true" + plugins = [plugins] if len(plugins) > limit: plugins = plugins[:limit] mapped_plugins = [{"Plugin ID": plugin.get("id"), "Plugin Name": plugin.get("name")} for plugin in plugins] @@ -2129,7 +2136,7 @@ def list_queries(client: Client, type, limit): return_message("No queries found.") queries = res.get('response') manageable_queries = queries.get("manageable", []) - usable_queries = queries.get("usable_queries", []) + usable_queries = queries.get("usable", []) mapped_queries, mapped_usable_queries = [], [] found_ids = [] @@ -2143,7 +2150,6 @@ def list_queries(client: Client, type, limit): "Query Manageable": "True" }) found_ids.append(query_id) - for usable_query in usable_queries: query_id = usable_query.get("id") if query_id not in found_ids: @@ -2207,7 +2213,6 @@ def main(): 'tenable-sc-list-groups': list_groups_command, 'tenable-sc-create-user': create_user_command, 'tenable-sc-update-user': update_user_command, - 'tenable-sc-delete-user': delete_user_command, 'tenable-sc-list-plugin-family': list_plugin_family_command, 'tenable-sc-create-policy': create_policy_command, diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index 91dd960900a0..9871b5180d93 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -1440,154 +1440,154 @@ script: execution: false name: tenable-sc-create-user outputs: - - contextPath: TenableSC.User.address + - contextPath: TenableSC.User.Address description: User address. type: String - - contextPath: TenableSC.User.apiKeys + - contextPath: TenableSC.User.ApiKeys description: User api keys. type: Unknown - - contextPath: TenableSC.User.authType + - contextPath: TenableSC.User.AuthType description: User auth type. type: String - - contextPath: TenableSC.User.canManage + - contextPath: TenableSC.User.CanManage description: User permissions. type: Boolean - - contextPath: TenableSC.User.canUse + - contextPath: TenableSC.User.CanUse description: User permissions. type: Boolean - - contextPath: TenableSC.User.city + - contextPath: TenableSC.User.City description: User city of residence. type: String - - contextPath: TenableSC.User.country + - contextPath: TenableSC.User.Country description: User country of residence. type: String - - contextPath: TenableSC.User.createdTime + - contextPath: TenableSC.User.CreatedTime description: User creation time. type: Date - - contextPath: TenableSC.User.email + - contextPath: TenableSC.User.Email description: User email address. type: String - - contextPath: TenableSC.User.failedLogins + - contextPath: TenableSC.User.FailedLogins description: User number of failed logins. type: String - - contextPath: TenableSC.User.fax + - contextPath: TenableSC.User.Fax description: User fax. type: String - - contextPath: TenableSC.User.fingerprint + - contextPath: TenableSC.User.Fingerprint description: User fingerprint. type: Unknown - - contextPath: TenableSC.User.firstname + - contextPath: TenableSC.User.Firstname description: User first name. type: String - - contextPath: TenableSC.User.group.description + - contextPath: TenableSC.User.group.Description description: User group's description. type: String - - contextPath: TenableSC.User.group.id + - contextPath: TenableSC.User.Group.ID description: User group's id. type: String - - contextPath: TenableSC.User.group.name + - contextPath: TenableSC.User.Group.Name description: User group's name. type: String - - contextPath: TenableSC.User.id + - contextPath: TenableSC.User.ID description: User id. type: String - - contextPath: TenableSC.User.lastLogin + - contextPath: TenableSC.User.LastLogin description: User last login time. type: String - - contextPath: TenableSC.User.lastLoginIP + - contextPath: TenableSC.User.LastLoginIP description: User last login IP. type: String - - contextPath: TenableSC.User.lastname + - contextPath: TenableSC.User.Lastname description: User last name. type: String - - contextPath: TenableSC.User.ldap.description + - contextPath: TenableSC.User.Ldap.Description description: User ldap description. type: String - - contextPath: TenableSC.User.ldap.id + - contextPath: TenableSC.User.Ldap.ID description: User ldap ID> type: Number - - contextPath: TenableSC.User.ldap.name + - contextPath: TenableSC.User.Ldap.Name description: User ldap name. type: String - - contextPath: TenableSC.User.ldapUsername + - contextPath: TenableSC.User.LdapUsername description: user ldap username. type: String - - contextPath: TenableSC.User.locked + - contextPath: TenableSC.User.Locked description: if user is locked or not. type: String - - contextPath: TenableSC.User.managedObjectsGroups.description + - contextPath: TenableSC.User.ManagedObjectsGroups.Description description: User managed object groups description. type: String - - contextPath: TenableSC.User.managedObjectsGroups.id + - contextPath: TenableSC.User.ManagedObjectsGroups.ID description: User managed object groups id type: String - - contextPath: TenableSC.User.managedObjectsGroups.name + - contextPath: TenableSC.User.ManagedObjectsGroups.Name description: User managed object groups name. type: String - - contextPath: TenableSC.User.managedUsersGroups.description + - contextPath: TenableSC.User.ManagedUsersGroups.Description description: User managed users groups description. type: String - - contextPath: TenableSC.User.managedUsersGroups.id + - contextPath: TenableSC.User.ManagedUsersGroups.ID description: User managed users groups id. type: String - - contextPath: TenableSC.User.managedUsersGroups.name + - contextPath: TenableSC.User.ManagedUsersGroups.Name description: User managed users groups name. type: String - - contextPath: TenableSC.User.modifiedTime + - contextPath: TenableSC.User.ModifiedTime description: User last modification time. type: Date - - contextPath: TenableSC.User.mustChangePassword + - contextPath: TenableSC.User.MustChangePassword description: If user must change password. type: String - - contextPath: TenableSC.User.password + - contextPath: TenableSC.User.Password description: If user password is set. type: String - - contextPath: TenableSC.User.phone + - contextPath: TenableSC.User.Phone description: User phone number. type: String - - contextPath: TenableSC.User.preferences.name + - contextPath: TenableSC.User.Preferences.Name description: User preferences name. type: String - - contextPath: TenableSC.User.preferences.tag + - contextPath: TenableSC.User.Preferences.Tag description: User preferences tag. type: String - - contextPath: TenableSC.User.preferences.value + - contextPath: TenableSC.User.Preferences.Value description: User preferences value. type: String - - contextPath: TenableSC.User.responsibleAsset.description + - contextPath: TenableSC.User.ResponsibleAsset.Description description: User responsible asset description. type: String - - contextPath: TenableSC.User.responsibleAsset.id + - contextPath: TenableSC.User.ResponsibleAsset.ID description: User responsible asset id. type: String - - contextPath: TenableSC.User.responsibleAsset.name + - contextPath: TenableSC.User.ResponsibleAsset.Name description: User responsible asset name. type: String - - contextPath: TenableSC.User.responsibleAsset.uuid - description: User responsible asset uuid. + - contextPath: TenableSC.User.ResponsibleAsset.UUID + description: User responsible asset UUID. type: Unknown - - contextPath: TenableSC.User.role.description + - contextPath: TenableSC.User.Role.Description description: User tole description. type: String - - contextPath: TenableSC.User.role.id + - contextPath: TenableSC.User.Role.ID description: User role id. type: String - - contextPath: TenableSC.User.role.name + - contextPath: TenableSC.User.Role.Name description: User role name type: String - - contextPath: TenableSC.User.state + - contextPath: TenableSC.User.State description: User state. type: String - - contextPath: TenableSC.User.status + - contextPath: TenableSC.User.Status description: User status. type: String - - contextPath: TenableSC.User.title + - contextPath: TenableSC.User.Title description: User title. type: String - - contextPath: TenableSC.User.username + - contextPath: TenableSC.User.Username description: User username. type: String - - contextPath: TenableSC.User.uuid + - contextPath: TenableSC.User.UUID description: User UUID. type: String - arguments: @@ -1740,194 +1740,156 @@ script: execution: false name: tenable-sc-update-user outputs: - - contextPath: TenableSC.User.address + - contextPath: TenableSC.User.Address description: User address. type: String - - contextPath: TenableSC.User.apiKeys + - contextPath: TenableSC.User.ApiKeys description: User api keys. type: Unknown - - contextPath: TenableSC.User.authType + - contextPath: TenableSC.User.AuthType description: User auth type. type: String - - contextPath: TenableSC.User.canManage + - contextPath: TenableSC.User.CanManage description: User permissions. type: Boolean - - contextPath: TenableSC.User.canUse + - contextPath: TenableSC.User.CanUse description: User permissions. type: Boolean - - contextPath: TenableSC.User.city + - contextPath: TenableSC.User.City description: User city of residence. type: String - - contextPath: TenableSC.User.country + - contextPath: TenableSC.User.Country description: User country of residence. type: String - - contextPath: TenableSC.User.createdTime + - contextPath: TenableSC.User.CreatedTime description: User creation time. type: Date - - contextPath: TenableSC.User.email + - contextPath: TenableSC.User.Email description: User email address. type: String - - contextPath: TenableSC.User.failedLogins + - contextPath: TenableSC.User.FailedLogins description: User number of failed logins. type: String - - contextPath: TenableSC.User.fax + - contextPath: TenableSC.User.Fax description: User fax. type: String - - contextPath: TenableSC.User.fingerprint + - contextPath: TenableSC.User.Fingerprint description: User fingerprint. type: Unknown - - contextPath: TenableSC.User.firstname + - contextPath: TenableSC.User.Firstname description: User first name. type: String - - contextPath: TenableSC.User.group.description + - contextPath: TenableSC.User.group.Description description: User group's description. type: String - - contextPath: TenableSC.User.group.id + - contextPath: TenableSC.User.Group.ID description: User group's id. type: String - - contextPath: TenableSC.User.group.name + - contextPath: TenableSC.User.Group.Name description: User group's name. type: String - - contextPath: TenableSC.User.id + - contextPath: TenableSC.User.ID description: User id. type: String - - contextPath: TenableSC.User.lastLogin + - contextPath: TenableSC.User.LastLogin description: User last login time. type: String - - contextPath: TenableSC.User.lastLoginIP + - contextPath: TenableSC.User.LastLoginIP description: User last login IP. type: String - - contextPath: TenableSC.User.lastname + - contextPath: TenableSC.User.Lastname description: User last name. type: String - - contextPath: TenableSC.User.ldap.description + - contextPath: TenableSC.User.Ldap.Description description: User ldap description. type: String - - contextPath: TenableSC.User.ldap.id + - contextPath: TenableSC.User.Ldap.ID description: User ldap ID> type: Number - - contextPath: TenableSC.User.ldap.name + - contextPath: TenableSC.User.Ldap.Name description: User ldap name. type: String - - contextPath: TenableSC.User.ldapUsername + - contextPath: TenableSC.User.LdapUsername description: user ldap username. type: String - - contextPath: TenableSC.User.locked + - contextPath: TenableSC.User.Locked description: if user is locked or not. type: String - - contextPath: TenableSC.User.managedObjectsGroups.description + - contextPath: TenableSC.User.ManagedObjectsGroups.Description description: User managed object groups description. type: String - - contextPath: TenableSC.User.managedObjectsGroups.id + - contextPath: TenableSC.User.ManagedObjectsGroups.ID description: User managed object groups id type: String - - contextPath: TenableSC.User.managedObjectsGroups.name + - contextPath: TenableSC.User.ManagedObjectsGroups.Name description: User managed object groups name. type: String - - contextPath: TenableSC.User.managedUsersGroups.description + - contextPath: TenableSC.User.ManagedUsersGroups.Description description: User managed users groups description. type: String - - contextPath: TenableSC.User.managedUsersGroups.id + - contextPath: TenableSC.User.ManagedUsersGroups.ID description: User managed users groups id. type: String - - contextPath: TenableSC.User.managedUsersGroups.name + - contextPath: TenableSC.User.ManagedUsersGroups.Name description: User managed users groups name. type: String - - contextPath: TenableSC.User.modifiedTime + - contextPath: TenableSC.User.ModifiedTime description: User last modification time. type: Date - - contextPath: TenableSC.User.mustChangePassword + - contextPath: TenableSC.User.MustChangePassword description: If user must change password. type: String - - contextPath: TenableSC.User.password + - contextPath: TenableSC.User.Password description: If user password is set. type: String - - contextPath: TenableSC.User.phone + - contextPath: TenableSC.User.Phone description: User phone number. type: String - - contextPath: TenableSC.User.preferences.name + - contextPath: TenableSC.User.Preferences.Name description: User preferences name. type: String - - contextPath: TenableSC.User.preferences.tag + - contextPath: TenableSC.User.Preferences.Tag description: User preferences tag. type: String - - contextPath: TenableSC.User.preferences.value + - contextPath: TenableSC.User.Preferences.Value description: User preferences value. type: String - - contextPath: TenableSC.User.responsibleAsset.description + - contextPath: TenableSC.User.ResponsibleAsset.Description description: User responsible asset description. type: String - - contextPath: TenableSC.User.responsibleAsset.id + - contextPath: TenableSC.User.ResponsibleAsset.ID description: User responsible asset id. type: String - - contextPath: TenableSC.User.responsibleAsset.name + - contextPath: TenableSC.User.ResponsibleAsset.Name description: User responsible asset name. type: String - - contextPath: TenableSC.User.responsibleAsset.uuid - description: User responsible asset uuid. + - contextPath: TenableSC.User.ResponsibleAsset.UUID + description: User responsible asset UUID. type: Unknown - - contextPath: TenableSC.User.role.description + - contextPath: TenableSC.User.Role.Description description: User tole description. type: String - - contextPath: TenableSC.User.role.id + - contextPath: TenableSC.User.Role.ID description: User role id. type: String - - contextPath: TenableSC.User.role.name + - contextPath: TenableSC.User.Role.Name description: User role name type: String - - contextPath: TenableSC.User.state + - contextPath: TenableSC.User.State description: User state. type: String - - contextPath: TenableSC.User.status + - contextPath: TenableSC.User.Status description: User status. type: String - - contextPath: TenableSC.User.title + - contextPath: TenableSC.User.Title description: User title. type: String - - contextPath: TenableSC.User.username + - contextPath: TenableSC.User.Username description: User username. type: String - - contextPath: TenableSC.User.uuid + - contextPath: TenableSC.User.UUID description: User UUID. type: String - - - - - arguments: - - default: false - defaultValue: '50' - description: Default is 50. The number of objects to return in one response (maximum limit is 200). - isArray: false - name: limit - required: false - secret: false - - default: false - description: The id of the query we wish to search. - isArray: false - name: query_id - required: false - secret: false - - default: false - description: The query time to retrieve. When no type is set all queries are returned. - isArray: false - name: type - required: false - secret: false - auto: PREDEFINED - predefined: - - alert - - lce - - mobile - - ticket - - user - deprecated: false - description: Lists queries. - execution: false - name: tenable-sc-list-query - outputs: - - - - arguments: - default: false description: The id of the user we want to delete. @@ -1940,7 +1902,6 @@ script: execution: false name: tenable-sc-delete-user outputs: - - arguments: - default: false description: The id of the plugin we want to search. If given, other arguments will be ignored. @@ -1970,7 +1931,21 @@ script: execution: false name: tenable-sc-list-plugin-family outputs: - + - contextPath: TenableSC.PluginFamily.ID + description: PluginFamily ID. + type: String + - contextPath: TenableSC.PluginFamily.Name + description: PluginFamily name. + type: String + - contextPath: TenableSC.PluginFamily.Count + description: Number of plugins in family. + type: String + - contextPath: TenableSC.PluginFamily.Plugins + description: The plugins list. + type: String + - contextPath: TenableSC.PluginFamily.Type + description: PluginFamily type. + type: String - arguments: - default: false description: The name of the policy you wish to create. @@ -1989,7 +1964,7 @@ script: defaultValue: 1 isArray: false name: policy_template_id - required: false + required: true secret: false - default: false description: 'Possible values: default, all or a comma separated list of values - 21,23,25,80,110.' @@ -2040,7 +2015,7 @@ script: description: Comma separated list of plugin_ids, Can be retrieved from the result of tenable-sc-list-plugin-family command with family_id as argument. isArray: false name: plugins_id - required: false + required: true secret: false - default: false description: Default is Automatic (normal). Rely on local port enumeration first before relying on network port scans. @@ -2060,8 +2035,505 @@ script: execution: false name: tenable-sc-create-policy outputs: - - + - contextPath: TenableSC.ScanPolicy.AuditFiles + description: Policy audit files. + type: Unknown + - contextPath: TenableSC.ScanPolicy.CanManage + description: Policy permissions. + type: String + - contextPath: TenableSC.ScanPolicy.CanUse + description: Policy permissions. + type: String + - contextPath: TenableSC.ScanPolicy.Context + description: Policy context. + type: String + - contextPath: TenableSC.ScanPolicy.CreatedTime + description: Policy creation time. + type: Date + - contextPath: TenableSC.ScanPolicy.Creator.Firstname + description: Policy creator first name. + type: String + - contextPath: TenableSC.ScanPolicy.Creator.ID + description: Policy creator ID. + type: String + - contextPath: TenableSC.ScanPolicy.Creator.Lastname + description: Policy creator last name. + type: String + - contextPath: TenableSC.ScanPolicy.Creator.Username + description: Policy creator user name. + type: String + - contextPath: TenableSC.ScanPolicy.Creator.UUID + description: Policy creator UUID. + type: String + - contextPath: TenableSC.ScanPolicy.Description + description: Policy description. + type: String + - contextPath: TenableSC.ScanPolicy.Families.Count + description: Policy number of families. + type: String + - contextPath: TenableSC.ScanPolicy.Families.ID + description: Policy family ID. + type: String + - contextPath: TenableSC.ScanPolicy.Families.Name + description: Policy family name. + type: String + - contextPath: TenableSC.ScanPolicy.Families.Plugins + description: Policy family plugins. + type: Unknown + - contextPath: TenableSC.ScanPolicy.GenerateXCCDFResults + description: Policy generated XCCDF results. + type: String + - contextPath: TenableSC.ScanPolicy.Groups + description: Policy groups. + type: Unknown + - contextPath: TenableSC.ScanPolicy.ID + description: Policy ID. + type: String + - contextPath: TenableSC.ScanPolicy.ModifiedTime + description: Policy last modification time. + type: Date + - contextPath: TenableSC.ScanPolicy.Name + description: Policy name. + type: String + - contextPath: TenableSC.ScanPolicy.Owner.Firstname + description: Policy owner first name. + type: String + - contextPath: TenableSC.ScanPolicy.Owner.ID + description: Policy owner ID. + type: String + - contextPath: TenableSC.ScanPolicy.Owner.Lastname + description: Policy owner last name. + type: String + - contextPath: TenableSC.ScanPolicy.Owner.Username + description: Policy owner user name. + type: String + - contextPath: TenableSC.ScanPolicy.Owner.UUID + description: Policy owner UUID. + type: String + - contextPath: TenableSC.ScanPolicy.OwnerGroup.Description + description: Policy owner group description. + type: String + - contextPath: TenableSC.ScanPolicy.OwnerGroup.ID + description: Policy owner group ID. + type: String + - contextPath: TenableSC.ScanPolicy.OwnerGroup.Name + description: Policy owner group name. + type: String + - contextPath: TenableSC.ScanPolicy.PolicyTemplate.Agent + description: Policy template agent. + type: String + - contextPath: TenableSC.ScanPolicy.PolicyTemplate.Description + description: Policy template description. + type: String + - contextPath: TenableSC.ScanPolicy.PolicyTemplate.ID + description: Policy template ID. + type: String + - contextPath: TenableSC.ScanPolicy.PolicyTemplate.Name + description: Policy template name. + type: String + - contextPath: TenableSC.ScanPolicy.Preferences.PortscanRange + description: Policy port scan range. + type: String + - contextPath: TenableSC.ScanPolicy.Preferences.SynFirewallDetection + description: Policy SYN Firewall detection. + type: String + - contextPath: TenableSC.ScanPolicy.Preferences.SynScanner + description: Policy SYN scanner. + type: String + - contextPath: TenableSC.ScanPolicy.Preferences.TcpScanner + description: Policy TCP scanner. + type: String + - contextPath: TenableSC.ScanPolicy.Preferences.UdpScanner + description: Policy UDP scanner. + type: String + - contextPath: TenableSC.ScanPolicy.Status + description: Policy status. + type: String + - contextPath: TenableSC.ScanPolicy.tags + description: Policy tags. + type: String + - contextPath: TenableSC.ScanPolicy.TargetGroup.Description + description: Policy target group description. + type: String + - contextPath: TenableSC.ScanPolicy.TargetGroup.ID + description: Policy target group ID. + type: Number + - contextPath: TenableSC.ScanPolicy.TargetGroup.Name + description: Policy target group name. + type: String + - contextPath: TenableSC.ScanPolicy.UUID + description: Policy UUID. + type: String + - arguments: + - default: false + description: The id of the query we wish to search. + isArray: false + name: query_id + required: false + secret: false + - default: false + description: The query time to retrieve. When no type is set all queries are returned. + isArray: false + name: type + required: false + secret: false + auto: PREDEFINED + predefined: + - alert + - lce + - mobile + - ticket + - user + deprecated: false + description: Lists queries. + execution: false + name: tenable-sc-list-query + outputs: + - contextPath: TenableSC.Query.Manageable.BrowseColumns + description: Relevant only when query_id is not given. Manageable Query browse columns. + type: String + - contextPath: TenableSC.Query.Manageable.BrowseSortColumn + description: Relevant only when query_id is not given. Manageable Query browse sort column. + type: String + - contextPath: TenableSC.Query.Manageable.BrowseSortDirection + description: Relevant only when query_id is not given. Manageable Query browse sort direction. + type: String + - contextPath: TenableSC.Query.Manageable.CanManage + description: Relevant only when query_id is not given. Manageable Query permissions. + type: String + - contextPath: TenableSC.Query.Manageable.CanUse + description: Relevant only when query_id is not given. Manageable Query permissions. + type: String + - contextPath: TenableSC.Query.Manageable.Context + description: Relevant only when query_id is not given. Manageable Query context. + type: String + - contextPath: TenableSC.Query.Manageable.CreatedTime + description: Relevant only when query_id is not given. Manageable Query creation time. + type: Date + - contextPath: TenableSC.Query.Manageable.Creator.Firstname + description: Relevant only when query_id is not given. Manageable Query Creator first name. + type: String + - contextPath: TenableSC.Query.Manageable.Creator.ID + description: Relevant only when query_id is not given. Manageable Query Creator ID. + type: String + - contextPath: TenableSC.Query.Manageable.Creator.Lastname + description: Relevant only when query_id is not given. Manageable Query Creator last name. + type: String + - contextPath: TenableSC.Query.Manageable.Creator.Username + description: Relevant only when query_id is not given. Manageable Query Creator user name. + type: String + - contextPath: TenableSC.Query.Manageable.Creator.UUID + description: Relevant only when query_id is not given. Manageable Query Creator UUID. + type: String + - contextPath: TenableSC.Query.Manageable.Description + description: Relevant only when query_id is not given. Manageable Query description. + type: String + - contextPath: TenableSC.Query.Manageable.Filters.FilterName + description: Relevant only when query_id is not given. Manageable Query filter name. + type: String + - contextPath: TenableSC.Query.Manageable.Filters.Operator + description: Relevant only when query_id is not given. Manageable Query filter operator. + type: String + - contextPath: TenableSC.Query.Manageable.Filters.Value + description: Relevant only when query_id is not given. Manageable Query filter value + type: String + - contextPath: TenableSC.Query.Manageable.Groups + description: Relevant only when query_id is not given. Manageable Query groups. + type: Unknown + - contextPath: TenableSC.Query.Manageable.ID + description: Relevant only when query_id is not given. Manageable Query ID. + type: String + - contextPath: TenableSC.Query.Manageable.ModifiedTime + description: Relevant only when query_id is not given. Manageable Query modification time. + type: Date + - contextPath: TenableSC.Query.Manageable.Name + description: Relevant only when query_id is not given. Manageable Query name + type: String + - contextPath: TenableSC.Query.Manageable.Owner.Firstname + description: Relevant only when query_id is not given. Manageable Query owner first name. + type: String + - contextPath: TenableSC.Query.Manageable.Owner.ID + description: Relevant only when query_id is not given. Manageable Query owner ID. + type: String + - contextPath: TenableSC.Query.Manageable.Owner.Lastname + description: Relevant only when query_id is not given. Manageable Query owner last name. + type: String + - contextPath: TenableSC.Query.Manageable.Owner.Username + description: Relevant only when query_id is not given. Manageable Query owner user name. + type: String + - contextPath: TenableSC.Query.Manageable.Owner.UUID + description: Relevant only when query_id is not given. Manageable Query owner UUID. + type: String + - contextPath: TenableSC.Query.Manageable.OwnerGroup.Description + description: Relevant only when query_id is not given. Manageable Query owner group description. + type: String + - contextPath: TenableSC.Query.Manageable.OwnerGroup.ID + description: Relevant only when query_id is not given. Manageable Query owner group ID. + type: String + - contextPath: TenableSC.Query.Manageable.OwnerGroup.Name + description: Relevant only when query_id is not given. Manageable Query owner group name. + type: String + - contextPath: TenableSC.Query.Manageable.Status + description: Relevant only when query_id is not given. Manageable Query status + type: String + - contextPath: TenableSC.Query.Manageable.Tags + description: Relevant only when query_id is not given. Manageable Query tags + type: String + - contextPath: TenableSC.Query.Manageable.TargetGroup.Description + description: Relevant only when query_id is not given. Manageable Query target group description. + type: String + - contextPath: TenableSC.Query.Manageable.TargetGroup.ID + description: Relevant only when query_id is not given. Manageable Query target group ID. + type: Number + - contextPath: TenableSC.Query.Manageable.TargetGroup.Name + description: Relevant only when query_id is not given. Manageable Query target group name. + type: String + - contextPath: TenableSC.Query.Manageable.Tool + description: Relevant only when query_id is not given. Manageable Query tool. + type: String + - contextPath: TenableSC.Query.Manageable.Type + description: Relevant only when query_id is not given. Manageable Query type. + type: String + - contextPath: TenableSC.Query.Manageable.Filters.Value.Description + description: Relevant only when query_id is not given. Manageable Query filter value description. + type: String + - contextPath: TenableSC.Query.Manageable.Filters.Value.ID + description: Relevant only when query_id is not given. Manageable Query filter value ID. + type: String + - contextPath: TenableSC.Query.Manageable.Filters.Value.Name + description: Relevant only when query_id is not given. Manageable Query filter value name. + type: String + - contextPath: TenableSC.Query.Manageable.Filters.Value.Type + description: Relevant only when query_id is not given. Manageable Query filter value type. + type: String + - contextPath: TenableSC.Query.Manageable.Filters.Value.UUID + description: Relevant only when query_id is not given. Manageable Query filter value UUID + type: String + - contextPath: TenableSC.Query.Manageable.Filters + description: Relevant only when query_id is not given. Manageable Query filters. + type: Unknown + - contextPath: TenableSC.Query.Usable.BrowseColumns + description: Relevant only when query_id is not given. Usable Query browse columns. + type: String + - contextPath: TenableSC.Query.Usable.BrowseSortColumn + description: Relevant only when query_id is not given. Usable Query browse sort column. + type: String + - contextPath: TenableSC.Query.Usable.BrowseSortDirection + description: Relevant only when query_id is not given. Usable Query browse sort direction. + type: String + - contextPath: TenableSC.Query.Usable.CanManage + description: Relevant only when query_id is not given. Usable Query permissions. + type: String + - contextPath: TenableSC.Query.Usable.CanUse + description: Relevant only when query_id is not given. Usable Query permissions. + type: String + - contextPath: TenableSC.Query.Usable.Context + description: Relevant only when query_id is not given. Usable Query context. + type: String + - contextPath: TenableSC.Query.Usable.CreatedTime + description: Relevant only when query_id is not given. Usable Query creation time. + type: Date + - contextPath: TenableSC.Query.Usable.Creator.Firstname + description: Relevant only when query_id is not given. Usable Query Creator first name. + type: String + - contextPath: TenableSC.Query.Usable.Creator.ID + description: Relevant only when query_id is not given. Usable Query Creator ID. + type: String + - contextPath: TenableSC.Query.Usable.Creator.Lastname + description: Relevant only when query_id is not given. Usable Query Creator last name. + type: String + - contextPath: TenableSC.Query.Usable.Creator.Username + description: Relevant only when query_id is not given. Usable Query Creator user name. + type: String + - contextPath: TenableSC.Query.Usable.Creator.UUID + description: Relevant only when query_id is not given. Usable Query Creator UUID. + type: String + - contextPath: TenableSC.Query.Usable.Description + description: Relevant only when query_id is not given. Usable Query description. + type: String + - contextPath: TenableSC.Query.Usable.Filters.FilterName + description: Relevant only when query_id is not given. Usable Query filter name. + type: String + - contextPath: TenableSC.Query.Usable.Filters.Operator + description: Relevant only when query_id is not given. Usable Query filter operator. + type: String + - contextPath: TenableSC.Query.Usable.Filters.Value + description: Relevant only when query_id is not given. Usable Query filter value + type: String + - contextPath: TenableSC.Query.Usable.Groups + description: Relevant only when query_id is not given. Usable Query groups. + type: Unknown + - contextPath: TenableSC.Query.Usable.ID + description: Relevant only when query_id is not given. Usable Query ID. + type: String + - contextPath: TenableSC.Query.Usable.ModifiedTime + description: Relevant only when query_id is not given. Usable Query modification time. + type: Date + - contextPath: TenableSC.Query.Usable.Name + description: Relevant only when query_id is not given. Usable Query name + type: String + - contextPath: TenableSC.Query.Usable.Owner.Firstname + description: Relevant only when query_id is not given. Usable Query owner first name. + type: String + - contextPath: TenableSC.Query.Usable.Owner.ID + description: Relevant only when query_id is not given. Usable Query owner ID. + type: String + - contextPath: TenableSC.Query.Usable.Owner.Lastname + description: Relevant only when query_id is not given. Usable Query owner last name. + type: String + - contextPath: TenableSC.Query.Usable.Owner.Username + description: Relevant only when query_id is not given. Usable Query owner user name. + type: String + - contextPath: TenableSC.Query.Usable.Owner.UUID + description: Relevant only when query_id is not given. Usable Query owner UUID. + type: String + - contextPath: TenableSC.Query.Usable.OwnerGroup.Description + description: Relevant only when query_id is not given. Usable Query owner group description. + type: String + - contextPath: TenableSC.Query.Usable.OwnerGroup.ID + description: Relevant only when query_id is not given. Usable Query owner group ID. + type: String + - contextPath: TenableSC.Query.Usable.OwnerGroup.Name + description: Relevant only when query_id is not given. Usable Query owner group name. + type: String + - contextPath: TenableSC.Query.Usable.Status + description: Relevant only when query_id is not given. Usable Query status + type: String + - contextPath: TenableSC.Query.Usable.Tags + description: Relevant only when query_id is not given. Usable Query tags + type: String + - contextPath: TenableSC.Query.Usable.TargetGroup.Description + description: Relevant only when query_id is not given. Usable Query target group description. + type: String + - contextPath: TenableSC.Query.Usable.TargetGroup.ID + description: Relevant only when query_id is not given. Usable Query target group ID. + type: Number + - contextPath: TenableSC.Query.Usable.TargetGroup.Name + description: Relevant only when query_id is not given. Usable Query target group name. + type: String + - contextPath: TenableSC.Query.Usable.Tool + description: Relevant only when query_id is not given. Usable Query tool. + type: String + - contextPath: TenableSC.Query.Usable.Type + description: Relevant only when query_id is not given. Usable Query type. + type: String + - contextPath: TenableSC.Query.Usable.Filters.Value.Description + description: Relevant only when query_id is not given. Usable Query filter value description. + type: String + - contextPath: TenableSC.Query.Usable.Filters.Value.ID + description: Relevant only when query_id is not given. Usable Query filter value ID. + type: String + - contextPath: TenableSC.Query.Usable.Filters.Value.Name + description: Relevant only when query_id is not given. Usable Query filter value name. + type: String + - contextPath: TenableSC.Query.Usable.Filters.Value.Type + description: Relevant only when query_id is not given. Usable Query filter value type. + type: String + - contextPath: TenableSC.Query.Usable.Filters.Value.UUID + description: Relevant only when query_id is not given. Usable Query filter value UUID + type: String + - contextPath: TenableSC.Query.Usable.Filters + description: Relevant only when query_id is not given. Usable Query filters. + type: Unknown + - contextPath: TenableSC.Query.BrowseColumns + description: Relevant only when query_id is given. Query browse columns. + type: String + - contextPath: TenableSC.Query.BrowseSortColumn + description: Relevant only when query_id is given. Query browse sort columns. + type: String + - contextPath: TenableSC.Query.BrowseSortDirection + description: Relevant only when query_id is given. Query browse sort direction + type: String + - contextPath: TenableSC.Query.CanManage + description: Relevant only when query_id is given. Query permissions. + type: String + - contextPath: TenableSC.Query.CanUse + description: Relevant only when query_id is given. Query permissions. + type: String + - contextPath: TenableSC.Query.Context + description: Relevant only when query_id is given. Query context. + type: String + - contextPath: TenableSC.Query.CreatedTime + description: Relevant only when query_id is given. Query creation time. + type: Date + - contextPath: TenableSC.Query.Creator.Firstname + description: Relevant only when query_id is given. Query creator first name. + type: String + - contextPath: TenableSC.Query.Creator.ID + description: Relevant only when query_id is given. Query creator ID. + type: String + - contextPath: TenableSC.Query.Creator.Lastname + description: Relevant only when query_id is given. Query creator last name. + type: String + - contextPath: TenableSC.Query.Creator.Username + description: Relevant only when query_id is given. Query creator user name. + type: String + - contextPath: TenableSC.Query.Creator.UUID + description: Relevant only when query_id is given. Query creator UUID. + type: String + - contextPath: TenableSC.Query.Description + description: Relevant only when query_id is given. Query description. + type: String + - contextPath: TenableSC.Query.Filters + description: Relevant only when query_id is given. Query filters. + type: Unknown + - contextPath: TenableSC.Query.Groups + description: Relevant only when query_id is given. Query groups. + type: Unknown + - contextPath: TenableSC.Query.ID + description: Relevant only when query_id is given. Query ID. + type: String + - contextPath: TenableSC.Query.ModifiedTime + description: Relevant only when query_id is given. Query modification time. + type: Date + - contextPath: TenableSC.Query.Name + description: Relevant only when query_id is given. Query name. + type: String + - contextPath: TenableSC.Query.Owner.Firstname + description: Relevant only when query_id is given. Query owner first name. + type: String + - contextPath: TenableSC.Query.Owner.ID + description: Relevant only when query_id is given. Query owner ID. + type: String + - contextPath: TenableSC.Query.Owner.Lastname + description: Relevant only when query_id is given. Query owner last name. + type: String + - contextPath: TenableSC.Query.Owner.Username + description: Relevant only when query_id is given. Query owner user name. + type: String + - contextPath: TenableSC.Query.Owner.UUID + description: Relevant only when query_id is given. Query owner UUID. + type: String + - contextPath: TenableSC.Query.OwnerGroup.Description + description: Relevant only when query_id is given. Query owner group description. + type: String + - contextPath: TenableSC.Query.OwnerGroup.ID + description: Relevant only when query_id is given. Query owner group ID. + type: String + - contextPath: TenableSC.Query.OwnerGroup.Name + description: Relevant only when query_id is given. Query owner group name. + type: String + - contextPath: TenableSC.Query.Status + description: Relevant only when query_id is given. Query status. + type: String + - contextPath: TenableSC.Query.Tags + description: Relevant only when query_id is given. Query tags. + type: String + - contextPath: TenableSC.Query.TargetGroup.Description + description: Relevant only when query_id is given. Query target group description. + type: String + - contextPath: TenableSC.Query.TargetGroup.ID + description: Relevant only when query_id is given. Query target group ID. + type: Number + - contextPath: TenableSC.Query.TargetGroup.Name + description: Relevant only when query_id is given. Query target group name. + type: String + - contextPath: TenableSC.Query.Tool + description: Relevant only when query_id is given. Query tool + type: String + - contextPath: TenableSC.Query.Type + description: Relevant only when query_id is given. Query type. + type: String - arguments: - default: false description: The id of the scan we wish to get the report on. Can be retrieved from list-scans command. From 467783ca61dc996606c30ee26db361ea85329e6b Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Wed, 17 May 2023 10:59:32 +0300 Subject: [PATCH 023/115] added command results --- .../Integrations/Tenable_sc/Tenable_sc.py | 578 ++++++++---------- .../Integrations/Tenable_sc/Tenable_sc.yml | 8 + 2 files changed, 258 insertions(+), 328 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 433844c1bc37..00acc314200a 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -593,11 +593,6 @@ def get_server_url(url): return url -def return_message(msg): - demisto.results(msg) - sys.exit(0) - - def validate_user_body_params(args, command_type): numbers_args_ls = ["group_id", "user_id", "responsible_asset_id"] @@ -654,12 +649,12 @@ def list_scans_command(client: Client, args: Dict[str, Any]): manageable = args.get('manageable', 'false').lower() if not res or 'response' not in res or not res['response']: - return_message('No scans found') + return_error('No scans found') scans_dicts = get_elements(res['response'], manageable) if len(scans_dicts) == 0: - return_message('No scans found') + return_error('No scans found') headers = ['ID', 'Name', 'Description', 'Policy', 'Group', 'Owner'] @@ -672,16 +667,13 @@ def list_scans_command(client: Client, args: Dict[str, Any]): 'Owner': s['owner'].get('username') } for s in scans_dicts] - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Tenable.sc Scans', mapped_scans, headers, removeNull=True), - 'EntryContext': { - 'TenableSC.Scan(val.ID===obj.ID)': createContext(mapped_scans, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(mapped_scans, removeNull=True), + outputs_prefix='TenableSC.Scan', + raw_response=res, + outputs_key_field='ID', + readable_output=tableToMarkdown('Tenable.sc Scans', mapped_scans, headers, removeNull=True) + ) def list_policies_command(client: Client, args: Dict[str, Any]): @@ -690,12 +682,12 @@ def list_policies_command(client: Client, args: Dict[str, Any]): manageable = args.get('manageable', 'false').lower() if not res or 'response' not in res or not res['response']: - return_message('No policies found') + return_error('No policies found') policies = get_elements(res['response'], manageable) if len(policies) == 0: - return_message('No policies found') + return_error('No policies found') headers = ['ID', 'Name', 'Description', 'Tag', 'Type', 'Group', 'Owner', 'LastModified'] @@ -710,28 +702,25 @@ def list_policies_command(client: Client, args: Dict[str, Any]): 'LastModified': timestamp_to_utc(p['modifiedTime']) } for p in policies] - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Tenable.sc Scan Policies', mapped_policies, headers, removeNull=True), - 'EntryContext': { - 'TenableSC.ScanPolicy(val.ID===obj.ID)': createContext(mapped_policies, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(mapped_policies, removeNull=True), + outputs_prefix='TenableSC.ScanPolicy', + raw_response=res, + outputs_key_field='ID', + readable_output=tableToMarkdown('Tenable.sc Scan Policies', mapped_policies, headers, removeNull=True) + ) def list_repositories_command(client: Client, args: Dict[str, Any]): res = client.get_repositories() if not res or 'response' not in res or not res['response']: - return_message('No repositories found') + return_error('No repositories found') repositories = res['response'] if len(repositories) == 0: - return_message('No repositories found') + return_error('No repositories found') headers = [ 'ID', @@ -741,16 +730,13 @@ def list_repositories_command(client: Client, args: Dict[str, Any]): mapped_repositories = [{'ID': r['id'], 'Name': r['name'], 'Description': r['description']} for r in repositories] - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Tenable.sc Scan Repositories', mapped_repositories, headers, removeNull=True), - 'EntryContext': { - 'TenableSC.ScanRepository(val.ID===obj.ID)': createContext(mapped_repositories, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(mapped_repositories, removeNull=True), + outputs_prefix='TenableSC.ScanRepository', + raw_response=res, + outputs_key_field='ID', + readable_output=tableToMarkdown('Tenable.sc Scan Repositories', mapped_repositories, headers, removeNull=True) + ) def list_credentials_command(client: Client, args: Dict[str, Any]): @@ -759,12 +745,12 @@ def list_credentials_command(client: Client, args: Dict[str, Any]): manageable = args.get('manageable', 'false').lower() if not res or 'response' not in res or not res['response']: - return_message('No credentials found') + return_error('No credentials found') credentials = get_elements(res['response'], manageable) if len(credentials) == 0: - return_message('No credentials found') + return_error('No credentials found') headers = ['ID', 'Name', 'Description', 'Type', 'Tag', 'Group', 'Owner', 'LastModified'] @@ -779,16 +765,13 @@ def list_credentials_command(client: Client, args: Dict[str, Any]): 'LastModified': timestamp_to_utc(c['modifiedTime']) } for c in credentials] - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Tenable.sc Credentials', mapped_credentials, headers, removeNull=True), - 'EntryContext': { - 'TenableSC.Credential(val.ID===obj.ID)': createContext(mapped_credentials, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(mapped_credentials, removeNull=True), + outputs_prefix='TenableSC.Credential', + raw_response=res, + outputs_key_field='ID', + readable_output=tableToMarkdown('Tenable.sc Credentials', mapped_credentials, headers, removeNull=True) + ) def list_assets_command(client: Client, args: Dict[str, Any]): @@ -797,12 +780,12 @@ def list_assets_command(client: Client, args: Dict[str, Any]): manageable = args.get('manageable', 'false').lower() if not res or 'response' not in res or not res['response']: - return_message('No assets found') + return_error('No assets found') assets = get_elements(res['response'], manageable) if len(assets) == 0: - return_message('No assets found') + return_error('No assets found') headers = ['ID', 'Name', 'Tag', 'Owner', 'Group', 'Type', 'HostCount', 'LastModified'] @@ -817,16 +800,13 @@ def list_assets_command(client: Client, args: Dict[str, Any]): 'LastModified': timestamp_to_utc(a['modifiedTime']) } for a in assets] - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Tenable.sc Assets', mapped_assets, headers, removeNull=True), - 'EntryContext': { - 'TenableSC.Asset(val.ID===obj.ID)': createContext(mapped_assets, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(mapped_assets, removeNull=True), + outputs_prefix='TenableSC.Asset', + raw_response=res, + outputs_key_field='ID', + readable_output=tableToMarkdown('Tenable.sc Assets', mapped_assets, headers, removeNull=True) + ) def get_asset_command(client: Client, args: Dict[str, Any]): @@ -835,7 +815,7 @@ def get_asset_command(client: Client, args: Dict[str, Any]): res = client.get_asset(asset_id) if not res or 'response' not in res: - return_message('Asset not found') + return_error('Asset not found') asset = res['response'] @@ -860,16 +840,13 @@ def get_asset_command(client: Client, args: Dict[str, Any]): 'IPs': ips } - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Tenable.sc Asset', mapped_asset, headers, removeNull=True), - 'EntryContext': { - 'TenableSC.Asset(val.ID===obj.ID)': createContext(mapped_asset, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(mapped_asset, removeNull=True), + outputs_prefix='TenableSC.Asset', + raw_response=res, + outputs_key_field='ID', + readable_output=tableToMarkdown('Tenable.sc Asset', mapped_asset, headers, removeNull=True) + ) def create_asset_command(client: Client, args: Dict[str, Any]): @@ -900,16 +877,13 @@ def create_asset_command(client: Client, args: Dict[str, Any]): 'Tags' ] - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Asset created successfully', mapped_asset, headers=headers, removeNull=True), - 'EntryContext': { - 'TenableSC.Asset(val.ID===obj.ID)': createContext(mapped_asset, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(mapped_asset, removeNull=True), + outputs_prefix='TenableSC.Asset', + raw_response=res, + outputs_key_field='ID', + readable_output=tableToMarkdown('Asset created successfully', mapped_asset, headers=headers, removeNull=True) + ) def delete_asset_command(client: Client, args: Dict[str, Any]): @@ -920,13 +894,10 @@ def delete_asset_command(client: Client, args: Dict[str, Any]): if not res: return_error('Error: Could not delete the asset') - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['text'], - 'HumanReadable': 'Asset successfully deleted' - }) + return CommandResults( + raw_response=res, + readable_output='Asset successfully deleted' + ) def list_report_definitions_command(client: Client, args: Dict[str, Any]): @@ -935,7 +906,7 @@ def list_report_definitions_command(client: Client, args: Dict[str, Any]): manageable = args.get('manageable', 'false').lower() if not res or 'response' not in res or not res['response']: - return_message('No report definitions found') + return_error('No report definitions found') reports = get_elements(res['response'], manageable) # Remove duplicates, take latest @@ -943,7 +914,7 @@ def list_report_definitions_command(client: Client, args: Dict[str, Any]): filter(lambda e: e['name'] == n, reports)) for n in {r['name'] for r in reports}] if len(reports) == 0: - return_message('No report definitions found') + return_error('No report definitions found') headers = ['ID', 'Name', 'Description', 'Type', 'Group', 'Owner'] @@ -960,22 +931,19 @@ def list_report_definitions_command(client: Client, args: Dict[str, Any]): for r in mapped_reports: del r['Description'] - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': hr, - 'EntryContext': { - 'TenableSC.ReportDefinition(val.ID===obj.ID)': createContext(mapped_reports, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(mapped_reports, removeNull=True), + outputs_prefix='TenableSC.ReportDefinition', + raw_response=res, + outputs_key_field='ID', + readable_output=hr + ) def list_zones_command(client: Client, args: Dict[str, Any]): res = client.get_zones() if not res or 'response' not in res: - return_message('No zones found') + return_error('No zones found') zones = res['response'] if len(zones) == 0: zones = [{ @@ -1013,16 +981,13 @@ def list_zones_command(client: Client, args: Dict[str, Any]): if mapped_scanners_total: hr += tableToMarkdown('Tenable.sc Scanners', mapped_scanners_total, headers, removeNull=True) - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': hr, - 'EntryContext': { - 'TenableSC.ScanZone(val.ID===obj.ID)': createContext(mapped_zones, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(mapped_zones, removeNull=True), + outputs_prefix='TenableSC.ScanZone', + raw_response=res, + outputs_key_field='ID', + readable_output=hr + ) def get_elements(elements, manageable): @@ -1096,16 +1061,13 @@ def create_scan_command(client: Client, args: Dict[str, Any]): 'Reports': demisto.dt(scan['reports'], 'id') } - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Scan created successfully', mapped_scan, headers, removeNull=True), - 'EntryContext': { - 'TenableSC.Scan(val.ID===obj.ID)': createContext(mapped_scan, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(mapped_scan, removeNull=True), + outputs_prefix='TenableSC.Scan', + raw_response=res, + outputs_key_field='ID', + readable_output=tableToMarkdown('Scan created successfully', mapped_scan, headers, removeNull=True) + ) def launch_scan_command(client: Client, args: Dict[str, Any]): @@ -1129,32 +1091,33 @@ def launch_scan_command(client: Client, args: Dict[str, Any]): 'Status': scan_result['status'] } - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Tenable.sc Scan', mapped_scan, headers, removeNull=True), - 'EntryContext': { - 'TenableSC.ScanResults(val.ID===obj.ID)': createContext(mapped_scan, removeNull=True) - } - }) - - -def launch_scan_report_command(client: Client, args: Dict[str, Any]): - res = launch_scan(client, args) - scan_id = res.get("response", {}).get("scanResult", {}).get("id") - poll_scan_status(client, {"scan_results_id": scan_id}) - - -@polling_function('tenable-sc-poll-scan-status') -def poll_scan_status(client: Client, args: Dict[str, Any]): + return CommandResults( + outputs=createContext(mapped_scan, removeNull=True), + outputs_prefix='TenableSC.ScanResults', + raw_response=res, + outputs_key_field='ID', + readable_output=tableToMarkdown('Tenable.sc Scan', mapped_scan, headers, removeNull=True) + ) + + +@polling_function('tenable-sc-launch-scan-report', requires_polling_arg=False, + poll_message="Scan is still running.") +def launch_scan_report_command(args: Dict[str, Any], client: Client): + first_execution = not args.get("scan_results_id") + if first_execution: + res = launch_scan(client, args) + scan_results_id = res.get("response", {}).get("scanResult", {}).get("id") + args["scan_results_id"] = scan_results_id + demisto.info(f"Running poll command for results id: {scan_results_id}") + else: + scan_results_id = args.get("scan_results_id") + args["hide_polling_output"] = True scan_results, _ = get_scan_status(client, args) is_scan_incomplete = scan_results[0].get("status") != "Completed" if is_scan_incomplete: - return PollResult(continue_to_poll=True, response=scan_results) + return PollResult(continue_to_poll=True, response=scan_results, args_for_next_run=args) else: - return PollResult(get_scan_report_command(client, {"scan_results_id": scan_results})) + return PollResult(get_scan_report_command(client, args)) def launch_scan(client: Client, args: Dict[str, Any]): @@ -1185,16 +1148,13 @@ def get_scan_status_command(client: Client, args: Dict[str, Any]): 'Description': scan_result['description'] } for scan_result in scans_results] - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Tenable.sc Scan Status', mapped_scans_results, headers, removeNull=True), - 'EntryContext': { - 'TenableSC.ScanResults(val.ID===obj.ID)': createContext(mapped_scans_results, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(mapped_scans_results, removeNull=True), + outputs_prefix='TenableSC.ScanResults', + raw_response=res, + outputs_key_field='ID', + readable_output=tableToMarkdown('Tenable.sc Scan Status', mapped_scans_results, headers, removeNull=True) + ) def get_scan_status(client: Client, args: Dict[str, Any]): @@ -1204,7 +1164,7 @@ def get_scan_status(client: Client, args: Dict[str, Any]): for scan_results_id in scan_results_ids: res = client.get_scan_results(scan_results_id) if not res or 'response' not in res or not res['response']: - return_message('Scan results not found') + return_error('Scan results not found') scans_results.append(res['response']) return scans_results, res @@ -1217,7 +1177,7 @@ def get_scan_report_command(client: Client, args: Dict[str, Any]): res = client.get_scan_report(scan_results_id) if not res or 'response' not in res or not res['response']: - return_message('Scan results not found') + return_error('Scan results not found') scan_results = res['response'] @@ -1259,16 +1219,13 @@ def get_scan_report_command(client: Client, args: Dict[str, Any]): hr += tableToMarkdown('Vulnerabilities', vulnerabilities, vuln_headers, removeNull=True) mapped_results['Vulnerability'] = vulnerabilities - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': hr, - 'EntryContext': { - 'TenableSC.ScanResults(val.ID===obj.ID)': createContext(mapped_results, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(mapped_results, removeNull=True), + outputs_prefix='TenableSC.ScanResults', + raw_response=res, + outputs_key_field='ID', + readable_output=hr + ) def list_plugins_command(client: Client, args: Dict[str, Any]): @@ -1279,7 +1236,7 @@ def list_plugins_command(client: Client, args: Dict[str, Any]): res = client.list_plugins(name, plugin_type, cve) if not res or 'response' not in res: - return_message('No plugins found') + return_error('No plugins found') plugins = res['response'] @@ -1292,16 +1249,13 @@ def list_plugins_command(client: Client, args: Dict[str, Any]): 'Family': p['family'].get('name') } for p in plugins] - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Tenable.sc Plugins', mapped_plugins, headers=headers, removeNull=True), - 'EntryContext': { - 'TenableSC.Plugin(val.ID===obj.ID)': createContext(mapped_plugins, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(mapped_plugins, removeNull=True), + outputs_prefix='TenableSC.Plugin', + raw_response=res, + outputs_key_field='ID', + readable_output=tableToMarkdown('Tenable.sc Plugins', mapped_plugins, headers=headers, removeNull=True) + ) def get_vulnerabilities(client: Client, scan_results_id): @@ -1400,7 +1354,7 @@ def get_vulnerability_command(client: Client, args: Dict[str, Any]): vuln_response = client.get_vulnerability(vuln_id) if not vuln_response or 'response' not in vuln_response: - return_message('Vulnerability not found') + return_error('Vulnerability not found') vuln = vuln_response['response'] vuln['severity'] = results[0]['severity'] # The vulnerability severity is the same in all the results @@ -1507,13 +1461,11 @@ def delete_scan_command(client: Client, args: Dict[str, Any]): if not res: return_error('Error: Could not delete the scan') - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['text'], - 'HumanReadable': 'Scan successfully deleted' - }) + return CommandResults( + outputs_prefix='TenableSC.Plugin', + raw_response=res, + readable_output='Scan successfully deleted' + ) def get_device_command(client: Client, args: Dict[str, Any]): @@ -1525,7 +1477,7 @@ def get_device_command(client: Client, args: Dict[str, Any]): res = client.get_device(uuid, ip, dns_name, repo) if not res or 'response' not in res: - return_message('Device not found') + return_error('Device not found') device = res['response'] @@ -1593,7 +1545,7 @@ def list_users_command(client: Client, args: Dict[str, Any]): res = client.get_users('id,username,firstname,lastname,title,email,createdTime,modifiedTime,lastLogin,role', user_id) if not res or 'response' not in res: - return_message('No users found') + return_error('No users found') users = res['response'] @@ -1607,7 +1559,7 @@ def list_users_command(client: Client, args: Dict[str, Any]): users = list(filter(lambda u: u['email'] == email, users)) if len(users) == 0: - return_message('No users found') + return_error('No users found') headers = [ 'ID', @@ -1635,16 +1587,13 @@ def list_users_command(client: Client, args: Dict[str, Any]): 'Role': u['role'].get('name') } for u in users] - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Tenable.sc Users', mapped_users, headers=headers, removeNull=True), - 'EntryContext': { - 'TenableSC.User(val.ID===obj.ID)': createContext(mapped_users, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(mapped_users, removeNull=True), + outputs_prefix='TenableSC.User', + raw_response=res, + outputs_key_field='ID', + readable_output=tableToMarkdown('Tenable.sc Users', mapped_users, headers=headers, removeNull=True) + ) def get_system_licensing_command(client: Client, args: Dict[str, Any]): @@ -1667,17 +1616,13 @@ def get_system_licensing_command(client: Client, args: Dict[str, Any]): 'ActiveIPS' ] - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Tenable.sc Licensing information', - mapped_licensing, headers=headers, removeNull=True), - 'EntryContext': { - 'TenableSC.Status': createContext(mapped_licensing, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(mapped_licensing, removeNull=True), + outputs_prefix='TenableSC.Status', + raw_response=res, + outputs_key_field='ID', + readable_output=tableToMarkdown('Tenable.sc Licensing information', mapped_licensing, headers=headers, removeNull=True) + ) def get_system_information_command(client: Client, args: Dict[str, Any]): @@ -1719,17 +1664,13 @@ def get_system_information_command(client: Client, args: Dict[str, Any]): 'LastCheck' ] - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': sys_res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Tenable.sc System information', - mapped_information, headers=headers, removeNull=True), - 'EntryContext': { - 'TenableSC.System(val.BuildID===obj.BuildID)': createContext(mapped_information, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(mapped_information, removeNull=True), + outputs_prefix='TenableSC.System', + raw_response=sys_res, + outputs_key_field='BuildID', + readable_output=tableToMarkdown('Tenable.sc System information', mapped_information, headers=headers, removeNull=True) + ) def list_alerts_command(client: Client, args: Dict[str, Any]): @@ -1738,12 +1679,12 @@ def list_alerts_command(client: Client, args: Dict[str, Any]): manageable = args.get('manageable', 'false').lower() if not res or 'response' not in res or not res['response']: - return_message('No alerts found') + return_error('No alerts found') alerts = get_elements(res['response'], manageable) if len(alerts) == 0: - return_message('No alerts found') + return_error('No alerts found') headers = ['ID', 'Name', 'Actions', 'State', 'LastTriggered', 'LastEvaluated', 'Group', 'Owner'] mapped_alerts = [{ @@ -1757,16 +1698,13 @@ def list_alerts_command(client: Client, args: Dict[str, Any]): 'Owner': a['owner'].get('username') } for a in alerts] - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Tenable.sc Alerts', mapped_alerts, headers=headers, removeNull=True), - 'EntryContext': { - 'TenableSC.Alert(val.ID===obj.ID)': createContext(mapped_alerts, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(mapped_alerts, removeNull=True), + outputs_prefix='TenableSC.Alert', + raw_response=res, + outputs_key_field='ID', + readable_output=tableToMarkdown('Tenable.sc Alerts', mapped_alerts, headers=headers, removeNull=True) + ) def get_alert_command(client: Client, args: Dict[str, Any]): @@ -1774,7 +1712,7 @@ def get_alert_command(client: Client, args: Dict[str, Any]): res = client.get_alerts(alert_id=alert_id) if not res or 'response' not in res or not res['response']: - return_message('Alert not found') + return_error('Alert not found') alert = res['response'] query_res = client.get_query(alert['query'].get('id')) @@ -1823,16 +1761,13 @@ def get_alert_command(client: Client, args: Dict[str, Any]): mapped_alert['Condition'] = mapped_condition - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': hr, - 'EntryContext': { - 'TenableSC.Alert(val.ID===obj.ID)': createContext(mapped_alert, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(mapped_alert, removeNull=True), + outputs_prefix='TenableSC.Alert', + raw_response=res, + outputs_key_field='ID', + readable_output=hr + ) def fetch_incidents(client: Client, first_fetch: str = '3 days'): @@ -1874,7 +1809,7 @@ def list_groups_command(client: Client, args: Dict[str, Any]): limit = int(args.get('limit', '50')) res = client.list_groups(show_users) if not res or not res.get('response', []): - return_message('No groups found') + return_error('No groups found') groups = res.get('response', []) if len(groups) > limit: groups = groups[:limit] @@ -1900,16 +1835,14 @@ def list_groups_command(client: Client, args: Dict[str, Any]): group_id = group.get('id') hr += f"{tableToMarkdown(f'Group id:{group_id}', users, headers, removeNull=True)}\n" groups = mapped_groups - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': hr, - 'EntryContext': { - 'TenableSC.Group(val.ID===obj.ID)': createContext(groups, removeNull=True) - } - }) + + return CommandResults( + outputs=createContext(groups, removeNull=True), + outputs_prefix='TenableSC.Group', + raw_response=res, + outputs_key_field='ID', + readable_output=hr + ) def get_all_scan_results_command(client: Client, args: Dict[str, Any]): @@ -1921,7 +1854,7 @@ def get_all_scan_results_command(client: Client, args: Dict[str, Any]): limit = 200 if not res or 'response' not in res or not res['response']: - return_message('Scan results not found') + return_error('Scan results not found') elements = get_elements(res['response'], get_manageable_results) @@ -1949,16 +1882,13 @@ def get_all_scan_results_command(client: Client, args: Dict[str, Any]): hr = tableToMarkdown(readable_title, scan_results, headers, removeNull=True, metadata='Total number of elements is {}'.format(len(elements))) - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': hr, - 'EntryContext': { - 'TenableSC.ScanResults(val.ID===obj.ID)': createContext(scan_results, removeNull=True) - } - }) + return CommandResults( + outputs=createContext(scan_results, removeNull=True), + outputs_prefix='TenableSC.ScanResults', + raw_response=res, + outputs_key_field='ID', + readable_output=hr + ) def create_user_command(client: Client, args: Dict[str, Any]): @@ -1978,7 +1908,7 @@ def update_user_command(client: Client, args: Dict[str, Any]): def process_update_and_create_user_response(res, hr_header): if not res or not res.get('response', {}): - return_message("User wasn't created successfully.") + return_error("User wasn't created successfully.") headers = ["User type", "User Id", "User Status", "User Name", "First Name", "Lat Name ", "Email ", "User Role Name", "User Group Name", "User LDAP Name"] response = res.get("response", {}) @@ -1994,28 +1924,24 @@ def process_update_and_create_user_response(res, hr_header): "User Group Name": response.get("group", {}).get("name"), "User LDAP Name": response.get("ldap", {}).get("name") } - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown(hr_header, mapped_response, headers, removeNull=True), - 'EntryContext': { - 'TenableSC.User(val.ID===obj.ID)': createContext(response, removeNull=True) - } - }) + + return CommandResults( + outputs=createContext(response, removeNull=True), + outputs_prefix='TenableSC.User', + raw_response=res, + outputs_key_field='ID', + readable_output=tableToMarkdown(hr_header, mapped_response, headers, removeNull=True) + ) def delete_user_command(client: Client, args: Dict[str, Any]): user_id = args.get('user_id') res = client.delete_user(user_id) - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['text'], - 'HumanReadable': f"User {user_id} is deleted." - }) + + return CommandResults( + raw_response=res, + readable_output="User {user_id} is deleted." + ) def list_plugin_family_command(client: Client, args: Dict[str, Any]): @@ -2024,7 +1950,7 @@ def list_plugin_family_command(client: Client, args: Dict[str, Any]): plugin_id = args.get('plugin_id', '') res = client.list_plugin_family(plugin_id, is_active) if not res or not res.get('response', []): - return_message('No plugins found') + return_error('No plugins found') plugins = res.get('response') if isinstance(plugins, dict): is_active = "false" if plugins.get("type") == "passive" else "true" @@ -2036,17 +1962,14 @@ def list_plugin_family_command(client: Client, args: Dict[str, Any]): for mapped_plugin in mapped_plugins: mapped_plugin["Is Active"] = is_active headers = ["Plugin ID", "Plugin Name", "Is Active"] - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Plugin families:', mapped_plugins, headers, removeNull=True), - 'EntryContext': { - 'TenableSC.PluginFamily(val.ID===obj.ID)': createContext(plugins, removeNull=True, - keyTransform=capitalize_first_letter) - } - }) + + return CommandResults( + outputs=createContext(plugins, removeNull=True, keyTransform=capitalize_first_letter), + outputs_prefix='TenableSC.PluginFamily', + raw_response=res, + outputs_key_field='ID', + readable_output=tableToMarkdown('Plugin families:', mapped_plugins, headers, removeNull=True) + ) def create_policy_command(client: Client, args: Dict[str, Any]): @@ -2081,17 +2004,14 @@ def create_policy_command(client: Client, args: Dict[str, Any]): headers = ["Policy type", "Policy Id", "name", "Description", "Created Time", "Plugin Families", "Policy Status", "Policy UUID", "Policy can Manage", "Creator Username", "Owner ID", "policyTemplate id", "policyTemplate Name"] - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Policy was created successfully:', mapped_created_policy, headers, removeNull=True), - 'EntryContext': { - 'TenableSC.Query(val.ID===obj.ID)': createContext(created_policy, removeNull=True, - keyTransform=capitalize_first_letter) - } - }) + + return CommandResults( + outputs=createContext(created_policy, removeNull=True, keyTransform=capitalize_first_letter), + outputs_prefix='TenableSC.Query', + raw_response=res, + outputs_key_field='ID', + readable_output=tableToMarkdown('Policy was created successfully:', mapped_created_policy, headers, removeNull=True) + ) def list_query_command(client: Client, args: Dict[str, Any]): @@ -2102,22 +2022,20 @@ def list_query_command(client: Client, args: Dict[str, Any]): res, hr, ec = get_query(client, query_id) else: res, hr, ec = list_queries(client, type, limit) - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': hr, - 'EntryContext': { - 'TenableSC.Query(val.ID===obj.ID)': createContext(ec, removeNull=True, keyTransform=capitalize_first_letter) - } - }) + + return CommandResults( + outputs=createContext(ec, removeNull=True, keyTransform=capitalize_first_letter), + outputs_prefix='TenableSC.Query', + raw_response=res, + outputs_key_field='ID', + readable_output=hr + ) def get_query(client: Client, query_id): res = client.get_query(query_id) if not res or not res.get('response', []): - return_message(f"The query {query_id} wasn't found") + return_error(f"The query {query_id} wasn't found") query = res.get('response') mapped_query = { "Query Id": query_id, @@ -2133,7 +2051,7 @@ def get_query(client: Client, query_id): def list_queries(client: Client, type, limit): res = client.list_queries(type) if not res or not res.get('response', []): - return_message("No queries found.") + return_error("No queries found.") queries = res.get('response') manageable_queries = queries.get("manageable", []) usable_queries = queries.get("usable", []) @@ -2201,9 +2119,7 @@ def main(): 'tenable-sc-launch-scan': launch_scan_command, 'tenable-sc-get-scan-status': get_scan_status_command, 'tenable-sc-get-scan-report': get_scan_report_command, - 'tenable-sc-get-vulnerability': get_vulnerability_command, 'tenable-sc-delete-scan': delete_scan_command, - 'tenable-sc-get-device': get_device_command, 'tenable-sc-list-users': list_users_command, 'tenable-sc-list-alerts': list_alerts_command, 'tenable-sc-get-alert': get_alert_command, @@ -2216,9 +2132,9 @@ def main(): 'tenable-sc-delete-user': delete_user_command, 'tenable-sc-list-plugin-family': list_plugin_family_command, 'tenable-sc-create-policy': create_policy_command, - 'tenable-sc-list-query': list_query_command, - 'tenable-sc-launch-scan-report': launch_scan_report_command, - 'tenable-sc-poll-scan-status': poll_scan_status + 'tenable-sc-list-query': list_query_command + # 'tenable-sc-get-vulnerability': get_vulnerability_command, + # 'tenable-sc-get-device': get_device_command, } try: @@ -2237,8 +2153,14 @@ def main(): elif command == 'fetch-incidents': first_fetch = params.get('fetch_time').strip() fetch_incidents(client, first_fetch) + elif command == 'tenable-sc-launch-scan-report': + return_results(launch_scan_report_command(args, client)) + elif command == 'tenable-sc-get-vulnerability': + return_results(get_vulnerability_command(client, args)) + elif command == 'tenable-sc-get-device': + return_results(get_device_command(client, args)) else: - command_dict[command](client, args) + return_results(command_dict[command](client, args)) except Exception as e: return_error( f'Failed to execute {command} command. Error: {str(e)}' diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index 9871b5180d93..663c52ddc9d7 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -2553,7 +2553,15 @@ script: name: diagnostic_password required: false secret: false + - default: false + description: Deprecated. Scan results id. + isArray: false + name: scan_results_id + required: false + secret: false + deprecated: true deprecated: false + polling: true description: Lists queries. execution: false name: tenable-sc-launch-scan-report From 4b7549df22e555007c8e09dded29bedeffc07315 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Wed, 17 May 2023 17:29:19 +0300 Subject: [PATCH 024/115] add dock strings --- .../Integrations/Tenable_sc/Tenable_sc.py | 983 ++++++++++++++---- .../Integrations/Tenable_sc/Tenable_sc.yml | 286 ++++- 2 files changed, 1068 insertions(+), 201 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 00acc314200a..d5929275f924 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -60,10 +60,33 @@ def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str self.login() def send_request_new(self, path, method='get', body={}, params={}, headers=None): + """ + Send the requests for access & secret keys authentication method. + Args: + path (str): The url suffix. + method (str): The request method. + body (dict): The request body. + params (dict): The request params. + headers (dict): The request headers. + Returns: + Dict: The response. + """ headers = headers or self.headers return self._http_request(method, url_suffix=path, params=params, data=json.dumps(body), headers=headers) def send_request_old(self, path, method='get', body=None, params=None, headers=None, try_number=1): + """ + Send the requests for username & password authentication method. + Args: + path (str): The url suffix. + method (str): The request method. + body (dict): The request body. + params (dict): The request params. + headers (dict): The request headers. + try_number (int): The request retries counter. + Returns: + Dict: The response. + """ body = body if body is not None else {} params = params if params is not None else {} headers = headers if headers is not None else self.headers @@ -95,6 +118,9 @@ def send_request_old(self, path, method='get', body=None, params=None, headers=N return res.json() def login(self): + """ + Set the token for username & password authentication method. + """ login_body = { 'username': self.user_name, 'password': self.password @@ -118,6 +144,13 @@ def login(self): demisto.setIntegrationContext({'token': self.token}) def send_login_request(self, login_body): + """ + Send the request to login for username & password authentication method. + Args: + login_body (dict): The request body. + Returns: + Dict: The response. + """ url = f'{self.url}/token' headers = { @@ -136,51 +169,73 @@ def send_login_request(self, login_body): return res.json() def logout(self): + """ + Send the request to logout for username & password authentication method. + """ self.send_request(path='token', method='delete') - def create_scan(self, name, repo_id, policy_id, plugin_id, description, zone_id, schedule, asset_ids, - ips, scan_virtual_hosts, report_ids, credentials, timeout_action, max_scan_time, - dhcp_track, rollover_type, dependent, start_time="", repeat_rule_freq="", repeat_rule_interval="", - repeat_rule_by_day="", enabled=True, time_zone=""): + def create_scan(self, args): + """ + Send the request for create_scan_command and create_remediation_scan_command. + Args: + args (dict): The demisto.args() object. + Returns: + Dict: The response. + """ + create_scan_mapping_dict = { + 'name': 'name', + 'pluginID': 'plugin_id', + 'description': 'description', + 'dhcpTracking': 'dhcp_tracking', + 'timeoutAction': 'timeout_action', + 'scanningVirtualHosts': 'scan_virtual_hosts', + 'rolloverType': 'rollover_type', + 'ipList': 'ip_list' + } + body = {key: args.get(value) for key, value in create_scan_mapping_dict.items() if args.get(value)} - scan_type = 'policy' if policy_id else 'plugin' + scan_type = args.get("scan_type") + body["type"] = scan_type if scan_type else ('policy' if args.get("policy_id") else 'plugin') - body = { - 'name': name, - 'type': scan_type, - 'repository': { - 'id': repo_id - } - } + if repo_id := args.get("repository_id"): + body["repository"] = {'id': repo_id} - if policy_id: - body['policy'] = { - 'id': policy_id - } + if policy_id := args.get("policy_id"): + body["policy"] = {'id': policy_id} - if plugin_id: - body['pluginID'] = plugin_id + if zone_id := args.get("zone_id"): + body["zone"] = {'id': zone_id} - if description: - body['description'] = description + if report_ids := args.get("report_ids"): + body['reports'] = [{'id': r_id, 'reportSource': 'individual'} for r_id in argToList(report_ids)] - if zone_id: - body['zone'] = { - 'id': zone_id - } + if asset_ids := args.get("asset_ids"): + if str(asset_ids).startswith('All'): + manageable = True if asset_ids == 'AllManageable' else False + res = self.get_assets(None) + assets = get_elements(res['response'], manageable) + asset_ids = list(map(lambda a: a['id'], assets)) + body['assets'] = [{'id': a_id} for a_id in argToList(asset_ids)] - if dhcp_track: - body['dhcpTracking'] = dhcp_track + if credentials := args.get("credentials"): + body['credentials'] = [{'id': c_id} for c_id in argToList(credentials)] + + if max_scan_time := args.get('max_scan_time'): + body['maxScanTime'] = max_scan_time * 3600 - if schedule: + if schedule := args.get('schedule'): body['schedule'] = { 'type': schedule } - if dependent: + if dependent := args.get('dependent_id'): body['schedule']['dependentID'] = dependent if schedule == 'ical': + start_time = args.get("start_time") + repeat_rule_freq = args.get("repeat_rule_freq", "") + repeat_rule_interval = int(args.get("repeat_rule_interval", 0)) + repeat_rule_by_day = argToList(args.get("repeat_rule_by_day"), "") timestamp_format = "%Y%m%dT%H%M%S" expected_format = "%Y-%m-%d:%H:%M:%S" try: @@ -188,7 +243,7 @@ def create_scan(self, name, repo_id, policy_id, plugin_id, description, zone_id, start_time = datetime.strftime(start_time, timestamp_format) except Exception: start_time = parse_date_range(start_time, date_format=timestamp_format)[0] - if time_zone and start_time: + if time_zone := args.get("time_zone") and start_time: body['schedule']['start'] = f"TZID={time_zone}:{start_time}" else: return_error("Please make sure to provide both time_zone and start_time.") @@ -200,45 +255,31 @@ def create_scan(self, name, repo_id, policy_id, plugin_id, description, zone_id, elif any([repeat_rule_freq, repeat_rule_interval, repeat_rule_by_day]): return_error("Please make sure to provide repeat_rule_freq, repeat_rule_interval with or without " "repeat_rule_by_day, or don't provide any of them.") - body['schedule']['enabled'] = enabled - - if report_ids: - body['reports'] = [{'id': r_id, 'reportSource': 'individual'} for r_id in argToList(report_ids)] - - if asset_ids: - if str(asset_ids).startswith('All'): - manageable = True if asset_ids == 'AllManageable' else False - res = self.get_assets(None) - assets = get_elements(res['response'], manageable) - asset_ids = list(map(lambda a: a['id'], assets)) - body['assets'] = [{'id': a_id} for a_id in argToList(asset_ids)] - - if credentials: - body['credentials'] = [{'id': c_id} for c_id in argToList(credentials)] - - if timeout_action: - body['timeoutAction'] = timeout_action - - if scan_virtual_hosts: - body['scanningVirtualHosts'] = scan_virtual_hosts - - if rollover_type: - body['rolloverType'] = rollover_type - - if ips: - body['ipList'] = ips - - if max_scan_time: - body['maxScanTime'] = max_scan_time * 3600 + body['schedule']['enabled'] = argToBoolean(args.get("enabled", True)) return self.send_request(path='scan', method='post', body=body) def get_scan_results(self, scan_results_id): + """ + Send the request for get_scan_status. + Args: + scan_results_id (str): The ID of the scan results to search. + Returns: + Dict: The response. + """ path = 'scanResult/' + scan_results_id return self.send_request(path) def launch_scan(self, scan_id, scan_target): + """ + Send the request for launch_scan_command and launch_scan_report_command. + Args: + scan_id (str): The ID of the scan to launch. + scan_target (str): Optional body parameters. + Returns: + Dict: The response. + """ path = 'scan/' + scan_id + '/launch' body = None if scan_target: @@ -250,11 +291,25 @@ def launch_scan(self, scan_id, scan_target): return self.send_request(path, 'post', body=body) def get_query(self, query_id): + """ + Send the request for get_alert_command and list_query_command. + Args: + query_id (str): The ID of the query to retrieve. + Returns: + Dict: The response. + """ path = f'query/{query_id}' return self.send_request(path) def list_queries(self, type): + """ + Send the request for list_query_command and list_queries. + Args: + type (str): The query type to retrieve. + Returns: + Dict: The response. + """ path = 'query' params = {} if type: @@ -263,6 +318,11 @@ def list_queries(self, type): return self.send_request(path=path, method="GET", params=params) def get_all_scan_results(self): + """ + Send the request for get_all_scan_results_command. + Returns: + Dict: The response. + """ params = { 'fields': 'name,description,details,status,scannedIPs,startTime,scanDuration,importStart,' 'finishTime,completedChecks,owner,ownerGroup,repository' @@ -270,6 +330,14 @@ def get_all_scan_results(self): return self.send_request(path='scanResult', params=params) def get_alerts(self, fields=None, alert_id=None): + """ + Send the request for list_alerts_command and get_alert_command. + Args: + fields (str): The fields to include in the response. + alert_id (str): The ID of the alert to search. + Returns: + Dict: The response. + """ path = 'alert' params = {} # type: Dict[str, Any] @@ -284,9 +352,21 @@ def get_alerts(self, fields=None, alert_id=None): return self.send_request(path, params=params) def get_system_licensing(self): + """ + Send the request for get_system_licensing_command. + Returns: + Dict: The response. + """ return self.send_request(path='status') def get_scans(self, fields): + """ + Send the request for list_scans_command. + Args: + fields (str): The fields to include in the response. + Returns: + Dict: The response. + """ params = None if fields: @@ -297,6 +377,13 @@ def get_scans(self, fields): return self.send_request(path='scan', params=params) def get_policies(self, fields): + """ + Send the request for list_policies_command. + Args: + fields (str): The fields to include in the response. + Returns: + Dict: The response. + """ params = None if fields: @@ -307,9 +394,21 @@ def get_policies(self, fields): return self.send_request(path='policy', params=params) def get_repositories(self): + """ + Send the request for list_repositories_command. + Returns: + Dict: The response. + """ return self.send_request(path='repository') def get_assets(self, fields): + """ + Send the request for list_assets_command and create_scan. + Args: + fields (str): The fields to include in the response. + Returns: + Dict: The response. + """ params = None if fields: @@ -320,6 +419,13 @@ def get_assets(self, fields): return self.send_request(path='asset', params=params) def get_credentials(self, fields): + """ + Send the request for list_credentials_command. + Args: + fields (str): The fields to include in the response. + Returns: + Dict: The response. + """ params = None if fields: @@ -330,6 +436,13 @@ def get_credentials(self, fields): return self.send_request(path='credential', params=params) def get_asset(self, asset_id): + """ + Send the request for list_assets_command. + Args: + asset_id (str): The ID of the asset to search. + Returns: + Dict: The response. + """ params = { 'fields': 'id,name,description,status,createdTime,modifiedTime,viewableIPs,ownerGroup,tags,owner' } @@ -337,6 +450,17 @@ def get_asset(self, asset_id): return self.send_request(path=f'asset/{asset_id}', params=params) def create_asset(self, name, description, owner_id, tags, ips): + """ + Send the request for create_asset_command. + Args: + name (str): The name for the asset. + description (str): The description for the asset. + owner_id (str): The Id of the owner of the asset. + tags (str): The tags for the asset. + ips (str): The IP list for the asset. + Returns: + Dict: The response. + """ body = { 'name': name, 'definedIPs': ips, @@ -355,9 +479,23 @@ def create_asset(self, name, description, owner_id, tags, ips): return self.send_request(path='asset', method='post', body=body) def delete_asset(self, asset_id): + """ + Send the request for delete_asset_command. + Args: + asset_id (str): The ID of the asset to delete. + Returns: + Dict: The response. + """ return self.send_request(path=f'asset/{asset_id}', method='delete') def get_report_definitions(self, fields): + """ + Send the request for list_report_definitions_command. + Args: + fields (str): The fields to include in the response. + Returns: + Dict: The response. + """ params = None if fields: @@ -368,9 +506,21 @@ def get_report_definitions(self, fields): return self.send_request(path='reportDefinition', params=params) def get_zones(self): + """ + Send the request for list_zones_command. + Returns: + Dict: The response. + """ return self.send_request(path='zone') def get_scan_report(self, scan_results_id): + """ + Send the request for get_scan_report_command and launch_scan_report_command. + Args: + scan_results_id (str): The ID of the scan_results to search for. + Returns: + Dict: The response. + """ path = 'scanResult/' + scan_results_id params = { @@ -380,7 +530,15 @@ def get_scan_report(self, scan_results_id): return self.send_request(path, params=params) - def create_query(self, scan_id, tool, query_filters=None): + def create_query(self, scan_id, tool): + """ + Send the request for get_vulnerabilities. + Args: + scan_id (str): The ID of the scan_results to create the query for. + tool (str): the tool to use. + Returns: + Dict: The response. + """ path = 'query' body = { @@ -390,12 +548,16 @@ def create_query(self, scan_id, tool, query_filters=None): 'scanID': scan_id } - if query_filters: - body['filters'] = query_filters - return self.send_request(path, method='post', body=body) def delete_query(self, query_id): + """ + Send the request for get_vulnerabilities. + Args: + query_id (str): The ID of the query to delete. + Returns: + Dict: The response. + """ if not query_id: return_error('query id returned None') path = 'query/' + str(query_id) @@ -420,33 +582,30 @@ def get_analysis(self, query, scan_results_id): return self.send_request(path, method='post', body=body) - def list_plugins(self, name, plugin_type, cve): - params = { - 'fields': 'id,type,name,description,family' - } - - if cve: - params['filterField'] = 'xrefs:CVE' - params['op'] = 'eq' - params['value'] = cve - - if plugin_type: - params['type'] = plugin_type - - return self.send_request(path='plugin', params=params) - def get_system_diagnostics(self): + """ + Send the request for get_system_information_command. + Returns: + Dict: The response. + """ return self.send_request(path='system/diagnostics') def get_system(self): + """ + Send the request for get_system_information_command. + Returns: + Dict: The response. + """ return self.send_request(path='system') - def change_scan_status(self, scan_results_id, status): - path = 'scanResult/' + scan_results_id + '/' + status - - return self.send_request(path, method='post') - def list_groups(self, show_users): + """ + Send the request for list_groups_command. + Args: + show_users (str): Optional filtering argument. + Returns: + Dict: The response. + """ params = {} if show_users: params['fields'] = 'users' @@ -454,6 +613,13 @@ def list_groups(self, show_users): return self.send_request(path='group', method='get', params=params) def get_vulnerability(self, vuln_id): + """ + Send the request for get_vulnerability_command. + Args: + vuln_id (str): The ID of the vulnerability to search. + Returns: + Dict: The response. + """ path = f'plugin/{vuln_id}' params = { @@ -464,9 +630,26 @@ def get_vulnerability(self, vuln_id): return self.send_request(path, params=params) def delete_scan(self, scan_id): + """ + Send the request for delete_scan_command. + Args: + scan_id (str): The ID of the scan to delete. + Returns: + Dict: The response. + """ return self.send_request(path=f'scan/{scan_id}', method='delete') def get_device(self, uuid, ip, dns_name, repo): + """ + Send the request for get_device_command. + Args: + uuid (str): The UUID of the device to search. + ip (str): Optional filtering argument. + dns_name (str): Optional filtering argument. + repo (str): Optional filtering argument. + Returns: + Dict: The response. + """ path = 'repository/' + repo + '/' if repo else '' path += 'deviceInfo' params = { @@ -483,6 +666,14 @@ def get_device(self, uuid, ip, dns_name, repo): return self.send_request(path, params=params) def get_users(self, fields, user_id): + """ + Send the request for list_users_command. + Args: + fields (str): The fields to include in the response. + user_id (str): The ID of the user to search. + Returns: + Dict: The response. + """ path = 'user' if user_id: @@ -498,19 +689,68 @@ def get_users(self, fields, user_id): return self.send_request(path, params=params) def create_user(self, args): + """ + Send the request for create_user_command. + Args: + args (Dict): The demisto.args() object. + Returns: + Dict: The response. + """ body = create_user_request_body(args) return self.send_request(path='user', body=body, method='POST') def update_user(self, args, user_id): + """ + Send the request for update_user_command. + Args: + args (Dict): The demisto.args() object. + user_id (str): The ID of the user to update. + Returns: + Dict: The response. + """ body = create_user_request_body(args) return self.send_request(path=f'user/{user_id}', body=body, method='PATCH') + def update_asset(self, args, asset_id): + """ + Send the request for update_asset_command. + Args: + args (Dict): The demisto.args() object. + asset_id (str): The ID of the asset to update. + Returns: + Dict: The response. + """ + body = { + "name": args.get("name"), + "description": args.get("description"), + "tags": args.get("tags"), + "ownerID": args.get("owner_id"), + "definedIPs": args.get("ip_list") + } + remove_nulls_from_dictionary(body) + return self.send_request(path=f'asset/{asset_id}', body=body, method='PATCH') + def delete_user(self, user_id): + """ + Send the request for delete_user_command. + Args: + user_id (str): The ID of the user to delete. + Returns: + Dict: The response. + """ return self.send_request(path=f'user/{user_id}', method='DELETE') def list_plugin_family(self, plugin_id, is_active): + """ + Send the request for list_plugin_family_command. + Args: + plugin_id (str): The id of the plugin to get. + is_active (str): Wether to filter by active / passive plugins. + Returns: + Dict: The response. + """ path = "pluginFamily" if plugin_id: path += f"/{plugin_id}" @@ -521,24 +761,35 @@ def list_plugin_family(self, plugin_id, is_active): path += "?fields=passive" return self.send_request(path=path, method='GET') - def create_policy(self, policy_name, policy_description, policy_template_id, port_scan_range, tcp_scanner, syn_scanner, - udp_scanner, syn_firewall_detection, family_id, plugins_id): + def create_policy(self, args): + """ + Send the request for create_policy_command. + Args: + args (Dict): the demisto.args() object. + Returns: + Dict: The response. + """ body = { - "name": policy_name, - "description": policy_description, + "name": args.get("policy_name"), + "description": args.get("policy_description"), "context": "scan", - "families": [{"id": family_id, "plugins": [{"id": id for id in plugins_id.split(',')}]}], "preferences": { - "portscan_range": port_scan_range, - "tcp_scanner": tcp_scanner, - "syn_scanner": syn_scanner, - "udp_scanner": udp_scanner, - "syn_firewall_detection": syn_firewall_detection + "portscan_range": args.get("port_scan_range", 'default'), + "tcp_scanner": args.get("tcp_scanner"), + "syn_scanner": args.get("syn_scanner"), + "udp_scanner": args.get("udp_scanner"), + "syn_firewall_detection": args.get("syn_firewall_detection", 'Automatic (normal)') }, "policyTemplate": { - "id": policy_template_id + "id": args.get("policy_template_id", '1') }, } + family = {"id": args.get("family_id", "")} + if plugins_id := args.get("plugins_id"): + family["plugins"] = [{"id": id for id in plugins_id.split(',')}] + body["families"] = [family] + remove_nulls_from_dictionary(body) + return self.send_request(path="policy", method='POST', body=body) @@ -553,6 +804,13 @@ def capitalize_first_letter(str): def create_user_request_body(args): + """ + Create user request body for update or create user commands. + Args: + args (Dict): the demisto.args() object. + Returns: + Dict: The request body. + """ user_query_mapping_dict: dict[str, str] = { "firstname": "first_name", "lastname": "last_name", @@ -588,12 +846,27 @@ def create_user_request_body(args): def get_server_url(url): + """ + Retrieve the server url. + Args: + url (str): The server url. + Returns: + str: The server url. + """ url = re.sub('/[\/]+$/', '', url) url = re.sub('\/$', '', url) return url def validate_user_body_params(args, command_type): + """ + Validate all given arguments are valid according to the command type (update or create). + Args: + args (Dict): the demisto.args() object. + command_type (Dict): the command type the function is called from (update or create) + Returns: + None: return error if arguments are invalid. + """ numbers_args_ls = ["group_id", "user_id", "responsible_asset_id"] time_zone = args.get("time_zone") @@ -629,6 +902,14 @@ def validate_user_body_params(args, command_type): def timestamp_to_utc(timestamp_str, default_returned_value=''): + """ + Convert timestamp string to UTC date time. + Args: + timestamp_str (str): timestamp string. + default_returned_value (str): the default return value + Returns: + str: UTC date time string. + """ if timestamp_str and (int(timestamp_str) > 0): # no value is when timestamp_str == '-1' return datetime.utcfromtimestamp(int(timestamp_str)).strftime( '%Y-%m-%dT%H:%M:%SZ') @@ -636,6 +917,14 @@ def timestamp_to_utc(timestamp_str, default_returned_value=''): def scan_duration_to_demisto_format(duration, default_returned_value=''): + """ + Convert duration to demisto format time. + Args: + duration (str): Scan duration in tenable sc format. + default_returned_value (str): the default return value + Returns: + Int / str: the scan duration in demisto format. + """ if duration: return float(duration) / 60 return default_returned_value @@ -645,6 +934,14 @@ def scan_duration_to_demisto_format(duration, default_returned_value=''): def list_scans_command(client: Client, args: Dict[str, Any]): + """ + List scans. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ res = client.get_scans('id,name,description,policy,ownerGroup,owner') manageable = args.get('manageable', 'false').lower() @@ -677,6 +974,14 @@ def list_scans_command(client: Client, args: Dict[str, Any]): def list_policies_command(client: Client, args: Dict[str, Any]): + """ + List policies. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ res = client.get_policies('id,name,description,tags,modifiedTime,owner,ownerGroup,policyTemplate') manageable = args.get('manageable', 'false').lower() @@ -712,6 +1017,14 @@ def list_policies_command(client: Client, args: Dict[str, Any]): def list_repositories_command(client: Client, args: Dict[str, Any]): + """ + List repositories. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ res = client.get_repositories() if not res or 'response' not in res or not res['response']: @@ -740,6 +1053,14 @@ def list_repositories_command(client: Client, args: Dict[str, Any]): def list_credentials_command(client: Client, args: Dict[str, Any]): + """ + List credentials. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ res = client.get_credentials('id,name,description,type,ownerGroup,owner,tags,modifiedTime') manageable = args.get('manageable', 'false').lower() @@ -775,6 +1096,14 @@ def list_credentials_command(client: Client, args: Dict[str, Any]): def list_assets_command(client: Client, args: Dict[str, Any]): + """ + List assets. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ res = client.get_assets('id,name,description,ipCount,type,tags,modifiedTime,groups,owner') manageable = args.get('manageable', 'false').lower() @@ -810,6 +1139,14 @@ def list_assets_command(client: Client, args: Dict[str, Any]): def get_asset_command(client: Client, args: Dict[str, Any]): + """ + Retrieve an asset by a given asset ID. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ asset_id = args.get('asset_id') res = client.get_asset(asset_id) @@ -820,7 +1157,7 @@ def get_asset_command(client: Client, args: Dict[str, Any]): asset = res['response'] ips = [] # type: List[str] - ip_lists = [v['ipList'] for v in asset['viewableIPs']] + ip_lists = [v['ipList'] for v in asset.get('viewableIPs', '')] for ip_list in ip_lists: # Extract IPs @@ -850,6 +1187,14 @@ def get_asset_command(client: Client, args: Dict[str, Any]): def create_asset_command(client: Client, args: Dict[str, Any]): + """ + Create an asset. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ name = args.get('name') description = args.get('description') owner_id = args.get('owner_id') @@ -887,6 +1232,14 @@ def create_asset_command(client: Client, args: Dict[str, Any]): def delete_asset_command(client: Client, args: Dict[str, Any]): + """ + Delete an asset by a given asset ID. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response and the human readable section. + """ asset_id = args.get('asset_id') res = client.delete_asset(asset_id) @@ -901,6 +1254,14 @@ def delete_asset_command(client: Client, args: Dict[str, Any]): def list_report_definitions_command(client: Client, args: Dict[str, Any]): + """ + Lists report defenitions. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ res = client.get_report_definitions('id,name,description,modifiedTime,type,ownerGroup,owner') manageable = args.get('manageable', 'false').lower() @@ -941,6 +1302,14 @@ def list_report_definitions_command(client: Client, args: Dict[str, Any]): def list_zones_command(client: Client, args: Dict[str, Any]): + """ + Lists zones + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ res = client.get_zones() if not res or 'response' not in res: return_error('No zones found') @@ -991,6 +1360,14 @@ def list_zones_command(client: Client, args: Dict[str, Any]): def get_elements(elements, manageable): + """ + Extracts a list from the given dictionary by given filter + Args: + elements (Dict): The dictionary to extract from + manageable (str): Wether to retrieve manageable or usable list + Returns: + List: The desired extracted list. + """ if manageable == 'false': return elements.get('usable') @@ -998,29 +1375,19 @@ def get_elements(elements, manageable): def create_scan_command(client: Client, args: Dict[str, Any]): - name = args.get('name') - repo_id = args.get('repository_id') - policy_id = args.get('policy_id') - plugin_id = args.get('plugin_id') - description = args.get('description') - zone_id = args.get('zone_id') + """ + Creates a scan. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ schedule = args.get('schedule') asset_ids = args.get('asset_ids') ips = args.get('ip_list') - scan_virtual_hosts = args.get('scan_virtual_hosts') - report_ids = args.get('report_ids') - credentials = args.get('credentials') - timeout_action = args.get('timeout_action') - max_scan_time = args.get('max_scan_time') - dhcp_track = args.get('dhcp_tracking') - rollover_type = args.get('rollover_type') dependent = args.get('dependent_id') time_zone = args.get("time_zone") - start_time = args.get("start_time") - repeat_rule_freq = args.get("repeat_rule_freq", "") - repeat_rule_interval = int(args.get("repeat_rule_interval", 0)) - repeat_rule_by_day = argToList(args.get("repeat_rule_by_day"), "") - enabled = argToBoolean(args.get("enabled", True)) if time_zone and time_zone not in pytz.all_timezones: return_error("Invalid time zone ID. Please choose one of the following: " @@ -1031,10 +1398,7 @@ def create_scan_command(client: Client, args: Dict[str, Any]): if schedule == 'dependent' and not dependent: return_error('Error: Dependent schedule must include a dependent scan ID') - res = client.create_scan(name, repo_id, policy_id, plugin_id, description, zone_id, schedule, asset_ids, - ips, scan_virtual_hosts, report_ids, credentials, timeout_action, max_scan_time, - dhcp_track, rollover_type, dependent, start_time, repeat_rule_freq, repeat_rule_interval, - repeat_rule_by_day, enabled, time_zone) + res = client.create_scan(args) if not res or 'response' not in res: return_error('Error: Could not retrieve the scan') @@ -1071,6 +1435,14 @@ def create_scan_command(client: Client, args: Dict[str, Any]): def launch_scan_command(client: Client, args: Dict[str, Any]): + """ + Launches a scan by a given scan ID. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ res = launch_scan(client, args) scan_result = res['response']['scanResult'] @@ -1100,9 +1472,19 @@ def launch_scan_command(client: Client, args: Dict[str, Any]): ) -@polling_function('tenable-sc-launch-scan-report', requires_polling_arg=False, - poll_message="Scan is still running.") +@polling_function(name='tenable-sc-launch-scan-report', + requires_polling_arg=False, + poll_message="Scan in progress.", + timeout=arg_to_number(demisto.args().get("timeout_in_seconds", '10800'))) def launch_scan_report_command(args: Dict[str, Any], client: Client): + """ + Polling command. Launch a scan by a given scan ID, following the scan status and retrieve the scan report. + Args: + args (Dict): demisto.args() object. + client (Client): The tenable.sc client object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ first_execution = not args.get("scan_results_id") if first_execution: res = launch_scan(client, args) @@ -1121,6 +1503,14 @@ def launch_scan_report_command(args: Dict[str, Any], client: Client): def launch_scan(client: Client, args: Dict[str, Any]): + """ + Launching a scan with a given scan ID. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + Dict: The response for the launch scan request. + """ scan_id = args.get('scan_id') target_address = args.get('diagnostic_target') target_password = args.get('diagnostic_password') @@ -1137,6 +1527,14 @@ def launch_scan(client: Client, args: Dict[str, Any]): def get_scan_status_command(client: Client, args: Dict[str, Any]): + """ + Return information about the scan status by a given scan results ID. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ scans_results, res = get_scan_status(client, args) headers = ['ID', 'Name', 'Status', 'Description'] @@ -1158,6 +1556,15 @@ def get_scan_status_command(client: Client, args: Dict[str, Any]): def get_scan_status(client: Client, args: Dict[str, Any]): + """ + Return information about the scan status by a given scan results ID. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + Dict: The relevant extracted section from the response. + Dict: The response. + """ scan_results_ids = argToList(args.get('scan_results_id')) scans_results = [] @@ -1171,6 +1578,14 @@ def get_scan_status(client: Client, args: Dict[str, Any]): def get_scan_report_command(client: Client, args: Dict[str, Any]): + """ + Return scan report information by a given scan results ID. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ scan_results_id = args.get('scan_results_id') vulnerabilities_to_get = argToList(args.get('vulnerability_severity', [])) @@ -1228,37 +1643,15 @@ def get_scan_report_command(client: Client, args: Dict[str, Any]): ) -def list_plugins_command(client: Client, args: Dict[str, Any]): - name = args.get('name'), - cve = args.get('cve'), - plugin_type = args.get('type') - - res = client.list_plugins(name, plugin_type, cve) - - if not res or 'response' not in res: - return_error('No plugins found') - - plugins = res['response'] - - headers = ['ID', 'Name', 'Type', 'Description', 'Family'] - mapped_plugins = [{ - 'ID': p['id'], - 'Name': p['name'], - 'Type': p['type'], - 'Description': p['description'], - 'Family': p['family'].get('name') - } for p in plugins] - - return CommandResults( - outputs=createContext(mapped_plugins, removeNull=True), - outputs_prefix='TenableSC.Plugin', - raw_response=res, - outputs_key_field='ID', - readable_output=tableToMarkdown('Tenable.sc Plugins', mapped_plugins, headers=headers, removeNull=True) - ) - - def get_vulnerabilities(client: Client, scan_results_id): + """ + Lists vulnerabilities from a scan by a given scan results ID. + Args: + client (Client): The tenable.sc client object. + scan_results_id (str): The ID of the scan results to get the information from. + Returns: + List: Sorted vulnerabilities list. + """ query = client.create_query(scan_results_id, 'vulnipdetail') if not query or 'response' not in query: @@ -1304,6 +1697,14 @@ def get_vulnerabilities(client: Client, scan_results_id): def get_vulnerability_command(client: Client, args: Dict[str, Any]): + """ + Return information about a vulnerability by a given vulnerability ID. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ vuln_id = args.get('vulnerability_id') scan_results_id = args.get('scan_results_id') sort_field = args.get('sort_field') @@ -1426,25 +1827,30 @@ def get_vulnerability_command(client: Client, args: Dict[str, Any]): 'ID': scan_results_id, 'Vulnerability': mapped_vuln, } - - context = {} - - context['TenableSC.ScanResults.Vulnerability(val.ID===obj.ID)'] = createContext(scan_result['Vulnerability'], removeNull=True) + command_results = [ + CommandResults( + outputs=createContext(scan_result['Vulnerability'], removeNull=True), + outputs_prefix='TenableSC.ScanResults.Vulnerability', + raw_response=vuln_response, + outputs_key_field='ID', + readable_output=hr + ) + ] if len(cves_output) > 0: - context['CVE(val.ID===obj.ID)'] = createContext(cves_output) + command_results.append(CommandResults(outputs=createContext(cves_output), outputs_prefix='CVE', outputs_key_field='ID')) - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': vuln_response, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': hr, - 'EntryContext': context - }) + return command_results def get_vulnerability_hosts_from_analysis(results): + """ + Lists the vulnerability hosts from given analysis. + Args: + results (Dict): The analysis results. + Returns: + List: list of all the vulnerability hosts extracted from the results. + """ return [{ 'IP': host['ip'], 'MAC': host['macAddress'], @@ -1454,6 +1860,14 @@ def get_vulnerability_hosts_from_analysis(results): def delete_scan_command(client: Client, args: Dict[str, Any]): + """ + Deletes a scan. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, and the human readable section. + """ scan_id = args.get('scan_id') res = client.delete_scan(scan_id) @@ -1462,13 +1876,20 @@ def delete_scan_command(client: Client, args: Dict[str, Any]): return_error('Error: Could not delete the scan') return CommandResults( - outputs_prefix='TenableSC.Plugin', raw_response=res, readable_output='Scan successfully deleted' ) def get_device_command(client: Client, args: Dict[str, Any]): + """ + Returns device info by a given device UUID. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ uuid = args.get('uuid') ip = args.get('ip') dns_name = args.get('dns_name') @@ -1524,20 +1945,33 @@ def get_device_command(client: Client, args: Dict[str, Any]): 'OS': mapped_device['OS'] } - demisto.results({ - 'Type': entryTypes['note'], - 'Contents': res, - 'ContentsFormat': formats['json'], - 'ReadableContentsFormat': formats['markdown'], - 'HumanReadable': tableToMarkdown('Tenable.sc Device', mapped_device, headers=headers, removeNull=True), - 'EntryContext': { - 'TenableSC.Device(val.UUID===obj.UUID)': createContext(mapped_device, removeNull=True), - 'Endpoint(val.IP===obj.IP)': createContext(endpoint, removeNull=True) - } - }) + command_results = [ + CommandResults( + outputs=createContext(mapped_device, removeNull=True), + outputs_prefix='TenableSC.Device', + raw_response=res, + outputs_key_field='UUID', + readable_output=tableToMarkdown('Tenable.sc Device', mapped_device, headers=headers, removeNull=True) + ), + CommandResults( + outputs=createContext(endpoint, removeNull=True), + outputs_prefix='Endpoint', + outputs_key_field='IP' + ) + ] + + return command_results def list_users_command(client: Client, args: Dict[str, Any]): + """ + Lists all users. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ user_id = args.get('id') username = args.get('username') email = args.get('email') @@ -1597,6 +2031,14 @@ def list_users_command(client: Client, args: Dict[str, Any]): def get_system_licensing_command(client: Client, args: Dict[str, Any]): + """ + Returns system licensing information. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ res = client.get_system_licensing() if not res or 'response' not in res: @@ -1626,6 +2068,14 @@ def get_system_licensing_command(client: Client, args: Dict[str, Any]): def get_system_information_command(client: Client, args: Dict[str, Any]): + """ + Return system information. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ sys_res = client.get_system() if not sys_res or 'response' not in sys_res: @@ -1674,6 +2124,14 @@ def get_system_information_command(client: Client, args: Dict[str, Any]): def list_alerts_command(client: Client, args: Dict[str, Any]): + """ + Lists all alerts. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ res = client.get_alerts(fields='id,name,description,didTriggerLastEvaluation,lastTriggered,' 'action,lastEvaluated,ownerGroup,owner') manageable = args.get('manageable', 'false').lower() @@ -1708,6 +2166,14 @@ def list_alerts_command(client: Client, args: Dict[str, Any]): def get_alert_command(client: Client, args: Dict[str, Any]): + """ + Return information about an alert by a given alert ID. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ alert_id = args.get('alert_id') res = client.get_alerts(alert_id=alert_id) @@ -1771,6 +2237,12 @@ def get_alert_command(client: Client, args: Dict[str, Any]): def fetch_incidents(client: Client, first_fetch: str = '3 days'): + """ + fetches incidents and upload them to demisto.incidents(). + Args: + client (Client): The tenable.sc client object. + first_fetch (str): The first_fetch integration param. + """ incidents = [] last_run = demisto.getLastRun() if not last_run: @@ -1805,6 +2277,14 @@ def fetch_incidents(client: Client, first_fetch: str = '3 days'): def list_groups_command(client: Client, args: Dict[str, Any]): + """ + Lists all groups + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ show_users = argToBoolean(args.get("show_users", True)) limit = int(args.get('limit', '50')) res = client.list_groups(show_users) @@ -1846,6 +2326,14 @@ def list_groups_command(client: Client, args: Dict[str, Any]): def get_all_scan_results_command(client: Client, args: Dict[str, Any]): + """ + Lists all scan results. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ res = client.get_all_scan_results() get_manageable_results = args.get('manageable', 'false').lower() # 'true' or 'false' page = int(args.get('page', '0')) @@ -1892,6 +2380,14 @@ def get_all_scan_results_command(client: Client, args: Dict[str, Any]): def create_user_command(client: Client, args: Dict[str, Any]): + """ + Create a user. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ validate_user_body_params(args, "create") res = client.create_user(args) hr_header = f'User {args.get("user_name")} was created successfully.' @@ -1899,14 +2395,30 @@ def create_user_command(client: Client, args: Dict[str, Any]): def update_user_command(client: Client, args: Dict[str, Any]): + """ + Update a user by given user ID. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ user_id = args.get('user_id') validate_user_body_params(args, "update") res = client.update_user(args, user_id) - hr_header = f'user {args.get("user_id")} was updated succesfully.' + hr_header = f'user {args.get("user_id")} was updated successfully.' process_update_and_create_user_response(res, hr_header) def process_update_and_create_user_response(res, hr_header): + """ + Process the response returned from the update and create user requests + Args: + res (Dict): The response returned from the request + hr_header (Dict): The header to add to the hr section. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ if not res or not res.get('response', {}): return_error("User wasn't created successfully.") headers = ["User type", "User Id", "User Status", "User Name", "First Name", "Lat Name ", "Email ", "User Role Name", @@ -1935,6 +2447,14 @@ def process_update_and_create_user_response(res, hr_header): def delete_user_command(client: Client, args: Dict[str, Any]): + """ + Delete a user by a given user ID. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, and human readable section. + """ user_id = args.get('user_id') res = client.delete_user(user_id) @@ -1945,6 +2465,14 @@ def delete_user_command(client: Client, args: Dict[str, Any]): def list_plugin_family_command(client: Client, args: Dict[str, Any]): + """ + return info about a query / list of queries. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ is_active = args.get('is_active') limit = int(args.get('limit', '50')) plugin_id = args.get('plugin_id', '') @@ -1973,18 +2501,15 @@ def list_plugin_family_command(client: Client, args: Dict[str, Any]): def create_policy_command(client: Client, args: Dict[str, Any]): - policy_name = args.get("policy") - policy_description = args.get("policy_description") - policy_template_id = args.get("policy_template_id", '1') - port_scan_range = args.get("port_scan_range", 'default') - tcp_scanner = args.get("tcp_scanner") - syn_scanner = args.get("syn_scanner") - udp_scanner = args.get("udp_scanner") - syn_firewall_detection = args.get("syn_firewall_detection", 'Automatic (normal)') - family_id = args.get("family_id") - plugins_id = args.get("plugins_id") - res = client.create_policy(policy_name, policy_description, policy_template_id, port_scan_range, tcp_scanner, syn_scanner, - udp_scanner, syn_firewall_detection, family_id, plugins_id) + """ + Creates a policy. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ + res = client.create_policy(args) created_policy = res.get("response") mapped_created_policy = { "Policy type": res.get("type"), @@ -2014,14 +2539,39 @@ def create_policy_command(client: Client, args: Dict[str, Any]): ) +def create_remediation_scan_command(client: Client, args: Dict[str, Any]): + """ + Creates remediation scan. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ + args["policy_template_id"] = '1' + res = client.create_policy(args) + args["scan_type"] = "policy" + args["schedule"] = "now" + # You can use either the asset_ids parameter or the ip_list parameter to specify assets, but you cannot use both parameters in a single request. + created_policy = res.get("response") + client.create_scan() + + def list_query_command(client: Client, args: Dict[str, Any]): + """ + return info about a query / list of queries. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response, human readable section, and the context entries to add. + """ type = args.get('type') - limit = int(args.get('limit', '50')) query_id = args.get('query_id', '') if query_id: res, hr, ec = get_query(client, query_id) else: - res, hr, ec = list_queries(client, type, limit) + res, hr, ec = list_queries(client, type) return CommandResults( outputs=createContext(ec, removeNull=True, keyTransform=capitalize_first_letter), @@ -2032,7 +2582,37 @@ def list_query_command(client: Client, args: Dict[str, Any]): ) +def update_asset_command(client: Client, args: Dict[str, Any]): + """ + Update an asset by a given asset ID. + Args: + client (Client): The tenable.sc client object. + args (Dict): demisto.args() object. + Returns: + CommandResults: command results object with the response and human readable. + """ + asset_id = args.get('asset_id') + res = client.update_asset(args, asset_id) + if not res or not res.get('response', []): + return_error(f"Couldn't update asset {asset_id}") + + return CommandResults( + raw_response=res, + readable_output=f'asset {asset_id} was updated successfully.' + ) + + def get_query(client: Client, query_id): + """ + get a query by ID and return the processed results. + Args: + client (Client): The tenable.sc client object. + query_id (str): The query ID to search. + Returns: + Dict: The response from the server. + str: The processed human readable. + Dict: The relevant section from the response. + """ res = client.get_query(query_id) if not res or not res.get('response', []): return_error(f"The query {query_id} wasn't found") @@ -2048,7 +2628,17 @@ def get_query(client: Client, query_id): return res, hr, query -def list_queries(client: Client, type, limit): +def list_queries(client: Client, type): + """ + Lists queries and return the processed results. + Args: + client (Client): The tenable.sc client object. + type (str): query time to filter by. + Returns: + Dict: The response from the server. + str: The processed human readable. + Dict: The relevant section from the response. + """ res = client.list_queries(type) if not res or not res.get('response', []): return_error("No queries found.") @@ -2111,7 +2701,6 @@ def main(): 'tenable-sc-list-zones': list_zones_command, 'tenable-sc-list-report-definitions': list_report_definitions_command, 'tenable-sc-list-assets': list_assets_command, - 'tenable-sc-list-plugins': list_plugins_command, 'tenable-sc-get-asset': get_asset_command, 'tenable-sc-create-asset': create_asset_command, 'tenable-sc-delete-asset': delete_asset_command, @@ -2132,9 +2721,11 @@ def main(): 'tenable-sc-delete-user': delete_user_command, 'tenable-sc-list-plugin-family': list_plugin_family_command, 'tenable-sc-create-policy': create_policy_command, - 'tenable-sc-list-query': list_query_command - # 'tenable-sc-get-vulnerability': get_vulnerability_command, - # 'tenable-sc-get-device': get_device_command, + 'tenable-sc-list-query': list_query_command, + 'tenable-sc-update-asset': update_asset_command, + 'tenable-sc-get-vulnerability': get_vulnerability_command, + 'tenable-sc-get-device': get_device_command, + 'tenable-sc-create-remediation-scan': create_remediation_scan_command } try: @@ -2155,10 +2746,6 @@ def main(): fetch_incidents(client, first_fetch) elif command == 'tenable-sc-launch-scan-report': return_results(launch_scan_report_command(args, client)) - elif command == 'tenable-sc-get-vulnerability': - return_results(get_vulnerability_command(client, args)) - elif command == 'tenable-sc-get-device': - return_results(get_device_command(client, args)) else: return_results(command_dict[command](client, args)) except Exception as e: diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index 663c52ddc9d7..5915afcbbbf9 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -695,7 +695,7 @@ script: - default: false description: 'A comma-separated list of days of the week to run the schedule at. Possible values are: SU, MO, TU, WE, TH, FR, SA.' isArray: yes - name: repeat_rule_interval + name: repeat_rule_by_day required: false secret: false - auto: PREDEFINED @@ -1247,7 +1247,7 @@ script: required: false secret: false deprecated: false - description: Available only for sec man. list all groups. + description: Demands sec man authentication. list all groups. execution: false name: tenable-sc-list-groups outputs: @@ -2172,7 +2172,7 @@ script: required: false secret: false - default: false - description: The query time to retrieve. When no type is set all queries are returned. + description: The query type to retrieve. When no type is set all queries are returned. isArray: false name: type required: false @@ -2534,6 +2534,279 @@ script: - contextPath: TenableSC.Query.Type description: Relevant only when query_id is given. Query type. type: String + - arguments: + - default: false + description: Asset name. + isArray: false + name: name + required: false + secret: false + - default: false + description: The ID of the asset we wish to update. + isArray: false + name: asset_id + required: true + secret: false + - default: false + description: The asset description. + isArray: false + name: description + required: false + secret: false + - default: false + description: The asset owner ID. + isArray: false + name: owner_id + required: false + secret: false + - default: false + description: The asset tag. + isArray: false + name: tag + required: false + secret: false + - default: false + description: Comma separated list of the asset IPs list. + isArray: false + name: ip_list + required: false + secret: false + deprecated: false + polling: true + description: Demands sec man authentication. Update an asset. + execution: false + name: tenable-sc-update-asset + outputs: + - arguments: + - default: false + description: The name of the policy you wish to create. + isArray: false + name: policy_name + required: false + secret: false + - default: false + description: The description of the policy you wish to create. + isArray: false + name: policy_description + required: false + secret: false + - default: false + description: 'Possible values: default, all or a comma separated list of values - 21,23,25,80,110.' + isArray: false + name: port_scan_range + required: false + secret: false + - default: false + description: Only possible if you are using Linux or FreeBSD. On Windows or macOS, the scanner does not do a TCP scan and instead uses the SYN scanner..If you enable this option, you can also set the syn_firewall_detection. + isArray: false + name: tcp_scanner + required: false + secret: false + auto: PREDEFINED + defaultValue: 'no' + predefined: + - 'no' + - 'yes' + - default: false + description: Identifies open TCP ports on the target hosts. If you enable this option, you can also set the syn_firewall_detection option. + isArray: false + name: syn_scanner + required: false + secret: false + auto: PREDEFINED + defaultValue: 'yes' + predefined: + - 'no' + - 'yes' + - default: false + description: Enabling the UDP port scanner may dramatically increase the scan time and produce unreliable results. Consider using the netstat or SNMP port enumeration options instead if possible. + isArray: false + name: udp_scanner + required: false + secret: false + auto: PREDEFINED + defaultValue: 'no' + predefined: + - 'no' + - 'yes' + - default: false + description: Default is Automatic (normal). Rely on local port enumeration first before relying on network port scans. + isArray: false + name: syn_firewall_detection + required: false + secret: false + auto: PREDEFINED + defaultValue: Automatic (normal) + predefined: + - Automatic (normal) + - Do not detect RST rate limitation(soft) + - Ignore closed ports(aggressive) + - Disabled(softer) + - default: false + description: Can be retrieved from the result of tenable-sc-list-plugin-family command . + isArray: false + name: family_id + required: true + secret: false + - default: false + description: Comma separated list of plugin_ids, Can be retrieved from the result of tenable-sc-list-plugin-family command with family_id as argument. + isArray: false + name: plugins_id + required: true + secret: false + - default: false + description: Scan name. + isArray: false + name: scan_name + required: true + secret: false + - default: false + description: Scan description. + isArray: false + name: description + required: false + secret: false + - default: false + description: Default is 1. Scan Repository ID, can be retrieved from list-repositories command. + isArray: false + name: repository_id + required: true + secret: false + defaultValue: '1' + - default: false + description: 'The timezone for the given start_time, possible values can be found here: https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html.' + isArray: false + name: time_zone + required: false + secret: false + - default: false + description: The scan start time, should be in the format of YYYY-MM-DD:HH:MM:SS or relative timestamp (i.e now, 3 days). + isArray: false + name: start_time + required: false + secret: false + - default: false + description: to specify repeating events based on an interval of a repeat_rule_freq or more. + isArray: false + name: repeat_rule_freq + required: false + secret: false + auto: PREDEFINED + predefined: + - HOURLY + - DAILY + - WEEKLY + - MONTHLY + - YEARLY + - default: false + description: 'the number of repeat_rule_freq between each interval (for example: If repeat_rule_freq=DAILY and repeat_rule_interval=8 it means every eight days.)' + isArray: false + name: repeat_rule_interval + required: false + secret: false + - default: false + description: 'A comma-separated list of days of the week to run the schedule at. Possible values are: SU, MO, TU, WE, TH, FR, SA.' + isArray: yes + name: repeat_rule_by_day + required: false + secret: false + - auto: PREDEFINED + default: false + description: Either no assets or comma separated asset IDs to scan, can be retrieved from list-assets command. + isArray: true + name: asset_ids + predefined: + required: false + secret: false + - auto: PREDEFINED + default: false + description: Default is false. Whether to includes virtual hosts, default false. + isArray: false + name: scan_virtual_hosts + predefined: + - 'true' + - 'false' + required: false + secret: false + defaultValue: false + - default: false + description: Comma separated IPs to scan e.g 10.0.0.1,10.0.0.2 . + isArray: false + name: ip_list + required: false + secret: false + - default: false + description: Comma separated list of report definition IDs to create post-scan, can be retrieved from list-report-definitions command. + isArray: true + name: report_ids + required: false + secret: false + - default: false + description: Comma separated credentials IDs to use, can be retrieved from list-credentials command. + isArray: true + name: credentials + required: false + secret: false + - auto: PREDEFINED + default: false + description: Default is import. Default. discard - do not import any of the results obtained by the scan to the database. import - Import the results of the current scan and discard the information for any unscanned targets. rollover-Import the results from the scan into the database and create a rollover scan that may be launched at a later time to complete the scan. + isArray: false + name: timeout_action + predefined: + - discard + - import + - rollover + required: false + defaultValue: import + secret: false + - default: false + description: Maximum scan run time in hours, default is 1. + isArray: false + name: max_scan_time + required: false + secret: false + - auto: PREDEFINED + default: false + description: Default is false. Track hosts which have been issued new IP address, (e.g. DHCP). + isArray: false + name: dhcp_tracking + predefined: + - 'true' + - 'false' + required: false + secret: false + defaultValue: false + - auto: PREDEFINED + default: false + description: The "enabled" field can only be set to "false" for schedules of type "ical". For all other schedules types, "enabled" is set to "true". + isArray: false + name: enabled + predefined: + - 'true' + - 'false' + required: false + secret: false + defaultValue: 'true' + - auto: PREDEFINED + default: false + description: Default is nextDay. Create a rollover scan scheduled to launch the next day at the same start time as the just completed scan. template-Create a rollover scan as a template for users to launch manually This field is required if the timeout_action is set to rollover. + isArray: false + name: rollover_type + required: false + secret: false + defaultValue: nextDay + deprecated: false + description: This command is prerequisite for creating remediation scan. creates policy. + execution: false + name: tenable-sc-create-remediation-scan + outputs: + + + + + + + - arguments: - default: false description: The id of the scan we wish to get the report on. Can be retrieved from list-scans command. @@ -2560,6 +2833,11 @@ script: required: false secret: false deprecated: true + - description: Default is 3 hours. The timeout in seconds until polling ends. + isArray: false + name: timeout_in_seconds + required: false + defaultValue: 10800 deprecated: false polling: true description: Lists queries. @@ -2567,6 +2845,8 @@ script: name: tenable-sc-launch-scan-report outputs: + + isfetch: true runonce: false From 4f91ecc3aea224f0a1ec39934c38eba7b1e184ad Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Thu, 18 May 2023 12:38:53 +0300 Subject: [PATCH 025/115] added rn --- .../Integrations/Tenable_sc/Tenable_sc.py | 56 ++++++++++++++++++- Packs/Tenable_sc/ReleaseNotes/1_0_10.md | 12 ++++ Packs/Tenable_sc/pack_metadata.json | 2 +- 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 Packs/Tenable_sc/ReleaseNotes/1_0_10.md diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index d5929275f924..ee9b0a201c49 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -1617,7 +1617,7 @@ def get_scan_report_command(client: Client, args: Dict[str, Any]): 'ScannedIPs': scan_results['scannedIPs'], 'Owner': scan_results['owner'].get('username'), 'RepositoryName': scan_results['repository'].get('name'), - 'Import Status': scan_results['importStatus'], + 'Import Status': scan_results.get('importStatus', ''), 'Is Scan Running ': scan_results['running'], 'Completed IPs': scan_results['progress']['completedIPs'] } @@ -2554,7 +2554,59 @@ def create_remediation_scan_command(client: Client, args: Dict[str, Any]): args["schedule"] = "now" # You can use either the asset_ids parameter or the ip_list parameter to specify assets, but you cannot use both parameters in a single request. created_policy = res.get("response") - client.create_scan() + args["policy_id"] = created_policy.get("id") + res = client.create_scan(args) + if not res or 'response' not in res: + return_error('Error: Could not retrieve the scan') + + scan = res['response'] + + headers = [ + 'Scan ID', + 'Scan Name', + 'Scan Description', + 'Scan Type', + 'Dhcp Tracking status', + 'Created Time', + 'Modified Time', + 'Max Scan Time', + 'Policy id ', + 'Policy context', + 'Policy description', + 'Schedule type', + 'Start Time', + 'Group', + 'Owner', + ] + + mapped_scan = { + 'Scan ID': scan['id'], + 'Scan Name': scan['name'], + 'Scan Description': scan['description'], + 'Scan Type': scan['type'], + 'Dhcp Tracking status': scan["dhcpTracking"], + 'Created Time': timestamp_to_utc(scan['createdTime']), + 'Modified Time': scan["modifiedTime"], + 'Max Scan Time': scan["maxScanTime"], + 'Policy id ': scan["policy"]["id"], + 'Policy context': scan["policy"]["context"], + 'Policy description': scan["policy"]["description"], + 'Schedule type': scan["schedule"]["type"], + 'Start Time': scan["schedule"]["start"], + 'Group': scan["ownerGroup"]["name"], + 'Owner': scan["owner"]["username"], + } + + + return CommandResults( + outputs=createContext(mapped_scan, removeNull=True), + outputs_prefix='TenableSC.Scan', + raw_response=res, + outputs_key_field='ID', + readable_output=tableToMarkdown('Scan created successfully', mapped_scan, headers, removeNull=True) + ) + + def list_query_command(client: Client, args: Dict[str, Any]): diff --git a/Packs/Tenable_sc/ReleaseNotes/1_0_10.md b/Packs/Tenable_sc/ReleaseNotes/1_0_10.md new file mode 100644 index 000000000000..7b4078e5e097 --- /dev/null +++ b/Packs/Tenable_sc/ReleaseNotes/1_0_10.md @@ -0,0 +1,12 @@ + +#### Integrations + +##### Tenable.sc + +- %%UPDATE_RN%% + +#### Playbooks + +##### Launch Scan - Tenable.sc + +- Deprecated. Use %%% instead. diff --git a/Packs/Tenable_sc/pack_metadata.json b/Packs/Tenable_sc/pack_metadata.json index 8039f5e36eb4..f79c291645b0 100644 --- a/Packs/Tenable_sc/pack_metadata.json +++ b/Packs/Tenable_sc/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Tenable.sc", "description": "With Tenable.sc (formerly SecurityCenter) you get a real-time, continuous assessment of your security posture so you can find and fix vulnerabilities faster.", "support": "xsoar", - "currentVersion": "1.0.9", + "currentVersion": "1.0.10", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "", From 6e583887fd5d5fb5b2d5073c880468289767def4 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Thu, 18 May 2023 16:47:57 +0300 Subject: [PATCH 026/115] all commands developed --- .../Integrations/Tenable_sc/Tenable_sc.py | 110 ++-- .../Integrations/Tenable_sc/Tenable_sc.yml | 603 +++++++++++++----- 2 files changed, 489 insertions(+), 224 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index ee9b0a201c49..c8c49f70b8a3 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -183,8 +183,6 @@ def create_scan(self, args): Dict: The response. """ create_scan_mapping_dict = { - 'name': 'name', - 'pluginID': 'plugin_id', 'description': 'description', 'dhcpTracking': 'dhcp_tracking', 'timeoutAction': 'timeout_action', @@ -197,6 +195,10 @@ def create_scan(self, args): scan_type = args.get("scan_type") body["type"] = scan_type if scan_type else ('policy' if args.get("policy_id") else 'plugin') + body['name'] = args.get('name') or args.get('scan_name') + + body["pluginID"] = args.get('plugin_id') or args.get('plugins_id') + if repo_id := args.get("repository_id"): body["repository"] = {'id': repo_id} @@ -1383,20 +1385,7 @@ def create_scan_command(client: Client, args: Dict[str, Any]): Returns: CommandResults: command results object with the response, human readable section, and the context entries to add. """ - schedule = args.get('schedule') - asset_ids = args.get('asset_ids') - ips = args.get('ip_list') - dependent = args.get('dependent_id') - time_zone = args.get("time_zone") - - if time_zone and time_zone not in pytz.all_timezones: - return_error("Invalid time zone ID. Please choose one of the following: " - "https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html") - if not asset_ids and not ips: - return_error('Error: Assets and/or IPs must be provided') - - if schedule == 'dependent' and not dependent: - return_error('Error: Dependent schedule must include a dependent scan ID') + validate_create_scan_inputs(args) res = client.create_scan(args) @@ -1434,6 +1423,30 @@ def create_scan_command(client: Client, args: Dict[str, Any]): ) +def validate_create_scan_inputs(args): + """ + Validate all given arguments are valid for create scan command. + Args: + args (Dict): the demisto.args() object. + Returns: + None: return error if arguments are invalid. + """ + schedule = args.get('schedule') + asset_ids = args.get('asset_ids') + ips = args.get('ip_list') + dependent = args.get('dependent_id') + time_zone = args.get("time_zone") + + if time_zone and time_zone not in pytz.all_timezones: + return_error("Invalid time zone ID. Please choose one of the following: " + "https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html") + if not asset_ids and not ips: + return_error('Error: Assets and/or IPs must be provided') + + if schedule == 'dependent' and not dependent: + return_error('Error: Dependent schedule must include a dependent scan ID') + + def launch_scan_command(client: Client, args: Dict[str, Any]): """ Launches a scan by a given scan ID. @@ -1602,24 +1615,24 @@ def get_scan_report_command(client: Client, args: Dict[str, Any]): vuln_headers = ['ID', 'Name', 'Family', 'Severity', 'Total'] mapped_results = { - 'Scan Type': res['type'], - 'ID': scan_results['id'], - 'Name': scan_results['name'], - 'Status': scan_results['status'], - 'Description': scan_results['description'], - 'Policy': scan_results['details'], + 'Scan Type': res.get('type', ''), + 'ID': scan_results.get('id', ''), + 'Name': scan_results.get('name', ''), + 'Status': scan_results.get('status', ''), + 'Description': scan_results.get('description', ''), + 'Policy': scan_results.get('details', ''), 'Group': scan_results.get('ownerGroup', {}).get('name'), - 'Checks': scan_results.get('completedChecks'), - 'StartTime': timestamp_to_utc(scan_results['startTime']), - 'EndTime': timestamp_to_utc(scan_results['finishTime']), - 'Duration': scan_duration_to_demisto_format(scan_results['scanDuration']), - 'ImportTime': timestamp_to_utc(scan_results['importStart']), - 'ScannedIPs': scan_results['scannedIPs'], - 'Owner': scan_results['owner'].get('username'), - 'RepositoryName': scan_results['repository'].get('name'), + 'Checks': scan_results.get('completedChecks', ''), + 'StartTime': timestamp_to_utc(scan_results.get('startTime', '')), + 'EndTime': timestamp_to_utc(scan_results.get('finishTime', '')), + 'Duration': scan_duration_to_demisto_format(scan_results.get('scanDuration', '')), + 'ImportTime': timestamp_to_utc(scan_results.get('importStart', '')), + 'ScannedIPs': scan_results.get('scannedIPs', ''), + 'Owner': scan_results.get('owner', {}).get('username', ''), + 'RepositoryName': scan_results.get('repository', {}).get('name', ''), 'Import Status': scan_results.get('importStatus', ''), - 'Is Scan Running ': scan_results['running'], - 'Completed IPs': scan_results['progress']['completedIPs'] + 'Is Scan Running ': scan_results.get('running', ''), + 'Completed IPs': scan_results.get('progress', {}).get('completedIPs', '') } hr = tableToMarkdown('Tenable.sc Scan ' + mapped_results['ID'] + ' Report', @@ -2549,35 +2562,21 @@ def create_remediation_scan_command(client: Client, args: Dict[str, Any]): CommandResults: command results object with the response, human readable section, and the context entries to add. """ args["policy_template_id"] = '1' - res = client.create_policy(args) args["scan_type"] = "policy" args["schedule"] = "now" - # You can use either the asset_ids parameter or the ip_list parameter to specify assets, but you cannot use both parameters in a single request. + validate_create_scan_inputs(args) + res = client.create_policy(args) created_policy = res.get("response") args["policy_id"] = created_policy.get("id") res = client.create_scan(args) if not res or 'response' not in res: return_error('Error: Could not retrieve the scan') - scan = res['response'] + scan = res.get('response', {}) - headers = [ - 'Scan ID', - 'Scan Name', - 'Scan Description', - 'Scan Type', - 'Dhcp Tracking status', - 'Created Time', - 'Modified Time', - 'Max Scan Time', - 'Policy id ', - 'Policy context', - 'Policy description', - 'Schedule type', - 'Start Time', - 'Group', - 'Owner', - ] + headers = ['Scan ID', 'Scan Name', 'Scan Description', 'Scan Type', 'Dhcp Tracking status', 'Created Time', 'Modified Time', + 'Max Scan Time', 'Policy id ', 'Policy context', 'Policy description', 'Schedule type', 'Start Time', 'Group', + 'Owner'] mapped_scan = { 'Scan ID': scan['id'], @@ -2597,18 +2596,15 @@ def create_remediation_scan_command(client: Client, args: Dict[str, Any]): 'Owner': scan["owner"]["username"], } - return CommandResults( - outputs=createContext(mapped_scan, removeNull=True), + outputs=createContext(scan, removeNull=True), outputs_prefix='TenableSC.Scan', raw_response=res, outputs_key_field='ID', - readable_output=tableToMarkdown('Scan created successfully', mapped_scan, headers, removeNull=True) + readable_output=tableToMarkdown('Remediation scan created successfully', mapped_scan, headers, removeNull=True) ) - - def list_query_command(client: Client, args: Dict[str, Any]): """ return info about a query / list of queries. diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index 5915afcbbbf9..d03cd904c29e 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -70,31 +70,31 @@ script: required: false secret: false deprecated: false - description: Get a list of Tenable.sc existing scans + description: Requires security manager authentication. Get a list of Tenable.sc existing scans execution: false name: tenable-sc-list-scans outputs: - contextPath: TenableSC.Scan.Name - description: Scan name + description: Scan name. type: string - contextPath: TenableSC.Scan.ID - description: Scan ID + description: Scan ID. type: number - contextPath: TenableSC.Scan.Description - description: Scan description + description: Scan description. type: string - contextPath: TenableSC.Scan.Policy - description: Scan policy name + description: Scan policy name. type: string - contextPath: TenableSC.Scan.Group - description: Scan policy owner group name + description: Scan policy owner group name. type: string - contextPath: TenableSC.Scan.Owner - description: Scan policy owner user name + description: Scan policy owner user name. type: string - arguments: - default: false - description: Scan ID, can be retrieved from list-scans command + description: Scan ID, can be retrieved from list-scans command. isArray: false name: scan_id required: true @@ -106,40 +106,40 @@ script: required: false secret: false - default: false - description: Non empty string password + description: Non empty string password. isArray: false name: diagnostic_password required: false secret: false deprecated: false - description: Launch an existing scan from Tenable.sc + description: Requires security manager authentication. Launch an existing scan from Tenable.sc execution: false name: tenable-sc-launch-scan outputs: - contextPath: TenableSC.ScanResults.Name - description: Scan name + description: Scan name. type: string - contextPath: TenableSC.ScanResults.ID - description: Scan Results ID + description: Scan Results ID. type: string - contextPath: TenableSC.ScanResults.OwnerID - description: Scan owner ID + description: Scan owner ID. type: string - contextPath: TenableSC.ScanResults.JobID - description: Job ID + description: Job ID. type: string - contextPath: TenableSC.ScanResults.Status - description: Scan status + description: Scan status. type: string - arguments: - default: false - description: Vulnerability ID from the scan-report command + description: Vulnerability ID from the scan-report command. isArray: false name: vulnerability_id required: true secret: false - default: false - description: Scan results ID from the scan-report command + description: Scan results ID from the scan-report command. isArray: false name: scan_results_id required: false @@ -195,178 +195,178 @@ script: required: false secret: false deprecated: false - description: Get details about a given vulnerability from a given Tenable.sc scan + description: Requires security manager authentication. Get details about a given vulnerability from a given Tenable.sc scan execution: false name: tenable-sc-get-vulnerability outputs: - contextPath: TenableSC.ScanResults.ID - description: Scan results ID + description: Scan results ID. type: number - contextPath: TenableSC.ScanResults.Vulnerability.ID - description: Vulnerability plugin ID + description: Vulnerability plugin ID. type: number - contextPath: TenableSC.ScanResults.Vulnerability.Name - description: Vulnerability name + description: Vulnerability name. type: string - contextPath: TenableSC.ScanResults.Vulnerability.Description - description: Vulnerability description + description: Vulnerability description. type: string - contextPath: TenableSC.ScanResults.Vulnerability.Type - description: Vulnerability type + description: Vulnerability type. type: string - contextPath: TenableSC.ScanResults.Vulnerability.Severity - description: Vulnerability Severity + description: Vulnerability Severity. type: string - contextPath: TenableSC.ScanResults.Vulnerability.Synopsis - description: Vulnerability Synopsis + description: Vulnerability Synopsis. type: string - contextPath: TenableSC.ScanResults.Vulnerability.Solution - description: Vulnerability Solution + description: Vulnerability Solution. type: string - contextPath: TenableSC.ScanResults.Vulnerability.Published - description: Vulnerability publish date + description: Vulnerability publish date. type: date - contextPath: TenableSC.ScanResults.Vulnerability.CPE - description: Vulnerability CPE + description: Vulnerability CPE. type: string - contextPath: TenableSC.ScanResults.Vulnerability.CVE - description: Vulnerability CVE + description: Vulnerability CVE. type: Unknown - contextPath: TenableSC.ScanResults.Vulnerability.ExploitAvailable - description: Vulnerability exploit available + description: Vulnerability exploit available. type: boolean - contextPath: TenableSC.ScanResults.Vulnerability.ExploitEase - description: Vulnerability exploit ease + description: Vulnerability exploit ease. type: string - contextPath: TenableSC.ScanResults.Vulnerability.RiskFactor - description: Vulnerability risk factor + description: Vulnerability risk factor. type: string - contextPath: TenableSC.ScanResults.Vulnerability.CVSSBaseScore - description: Vulnerability CVSS base score + description: Vulnerability CVSS base score. type: number - contextPath: TenableSC.ScanResults.Vulnerability.CVSSTemporalScore - description: Vulnerability CVSS temporal score + description: Vulnerability CVSS temporal score. type: number - contextPath: TenableSC.ScanResults.Vulnerability.CVSSVector - description: Vulnerability CVSS vector + description: Vulnerability CVSS vector. type: string - contextPath: TenableSC.ScanResults.Vulnerability.PluginDetails - description: Vulnerability plugin details + description: Vulnerability plugin details. type: Unknown - contextPath: CVE.ID - description: CVE ID + description: CVE ID. type: Unknown - contextPath: TenableSC.ScanResults.Vulnerability.Host.IP - description: Vulnerability Host IP + description: Vulnerability Host IP. type: string - contextPath: TenableSC.ScanResults.Vulnerability.Host.MAC - description: Vulnerability Host MAC + description: Vulnerability Host MAC. type: string - contextPath: TenableSC.ScanResults.Vulnerability.Host.Port - description: Vulnerability Host Port + description: Vulnerability Host Port. type: number - contextPath: TenableSC.ScanResults.Vulnerability.Host.Protocol - description: Vulnerability Host Protocol + description: Vulnerability Host Protocol. type: string - arguments: - default: false - description: Scan results ID from the launch-scan command + description: Scan results ID from the launch-scan command. isArray: true name: scan_results_id required: true secret: false deprecated: false - description: Get the status of a specific scan in Tenable.sc + description: Requires security manager authentication. Get the status of a specific scan in Tenable.sc. execution: false name: tenable-sc-get-scan-status outputs: - contextPath: TenableSC.ScanResults.Status - description: Scan status + description: Scan status. type: string - contextPath: TenableSC.ScanResults.Name - description: Scan Name + description: Scan Name. type: string - contextPath: TenableSC.ScanResults.Description - description: Scan description + description: Scan description. type: Unknown - contextPath: TenableSC.ScanResults.ID - description: Scan results ID + description: Scan results ID. type: Unknown - arguments: - default: false - description: Scan results ID + description: Scan results ID. isArray: false name: scan_results_id required: true secret: false - default: false defaultValue: Critical,High,Medium,Low,Info - description: Comma separated list of severity values of vulnerabilities to retrieve + description: Comma separated list of severity values of vulnerabilities to retrieve. isArray: true name: vulnerability_severity required: false secret: false deprecated: false - description: Get a single report with Tenable.sc scan results + description: Requires security manager authentication. Get a single report with Tenable.sc scan results. execution: false name: tenable-sc-get-scan-report outputs: - contextPath: TenableSC.ScanResults.ID - description: Scan results ID + description: Scan results ID. type: number - contextPath: TenableSC.ScanResults.Name - description: Scan name + description: Scan name. type: string - contextPath: TenableSC.ScanResults.Status - description: Scan status + description: Scan status. type: string - contextPath: TenableSC.ScanResults.ScannedIPs - description: Scan number of scanned IPs + description: Scan number of scanned IPs. type: number - contextPath: TenableSC.ScanResults.StartTime - description: Scan start time + description: Scan start time. type: date - contextPath: TenableSC.ScanResults.EndTime - description: Scan end time + description: Scan end time. type: date - contextPath: TenableSC.ScanResults.Checks - description: Scan completed checks + description: Scan completed checks. type: number - contextPath: TenableSC.ScanResults.RepositoryName - description: Scan repository name + description: Scan repository name. type: string - contextPath: TenableSC.ScanResults.Description - description: Scan description + description: Scan description. type: string - contextPath: TenableSC.ScanResults.Vulnerability.ID - description: Scan vulnerability ID + description: Scan vulnerability ID. type: number - contextPath: TenableSC.ScanResults.Vulnerability.Name - description: Scan vulnerability Name + description: Scan vulnerability Name. type: string - contextPath: TenableSC.ScanResults.Vulnerability.Family - description: Scan vulnerability family + description: Scan vulnerability family. type: string - contextPath: TenableSC.ScanResults.Vulnerability.Severity - description: Scan vulnerability severity + description: Scan vulnerability severity. type: string - contextPath: TenableSC.ScanResults.Vulnerability.Total - description: Scan vulnerability total hosts + description: Scan vulnerability total hosts. type: number - contextPath: TenableSC.ScanResults.Policy - description: Scan policy + description: Scan policy. type: string - contextPath: TenableSC.ScanResults.Group - description: Scan owner group name + description: Scan owner group name. type: string - contextPath: TenableSC.ScanResults.Owner - description: Scan owner user name + description: Scan owner user name. type: string - contextPath: TenableSC.ScanResults.Duration - description: Scan duration in minutes + description: Scan duration in minutes. type: number - contextPath: TenableSC.ScanResults.ImportTime - description: Scan import time + description: Scan import time. type: date - arguments: - auto: PREDEFINED @@ -381,33 +381,33 @@ script: required: false secret: false deprecated: false - description: Get a list of Tenable.sc credentials + description: Requires security manager authentication. Get a list of Tenable.sc credentials. execution: false name: tenable-sc-list-credentials outputs: - contextPath: TenableSC.Credential.Name - description: Credential name + description: Credential name. type: string - contextPath: TenableSC.Credential.ID - description: Credential ID + description: Credential ID. type: number - contextPath: TenableSC.Credential.Description - description: Credential description + description: Credential description. type: string - contextPath: TenableSC.Credential.Type - description: Credential type + description: Credential type. type: string - contextPath: TenableSC.Credential.Tag - description: Credential tag + description: Credential tag. type: string - contextPath: TenableSC.Credential.Group - description: Credential owner group name + description: Credential owner group name. type: string - contextPath: TenableSC.Credential.Owner - description: Credential owner user name + description: Credential owner user name. type: string - contextPath: TenableSC.Credential.LastModified - description: Credential last modified time + description: Credential last modified time. type: date - arguments: - auto: PREDEFINED @@ -422,33 +422,33 @@ script: required: false secret: false deprecated: false - description: Get a list of Tenable.sc scan policies + description: Requires security manager authentication. Get a list of Tenable.sc scan policies. execution: false name: tenable-sc-list-policies outputs: - contextPath: TenableSC.ScanPolicy.Name - description: Scan policy name + description: Scan policy name. type: string - contextPath: TenableSC.ScanPolicy.ID - description: Scan policy ID + description: Scan policy ID. type: number - contextPath: TenableSC.ScanPolicy.Description - description: Scan policy description + description: Scan policy description. type: string - contextPath: TenableSC.ScanPolicy.Tag - description: Scan policy tag + description: Scan policy tag. type: string - contextPath: TenableSC.ScanPolicy.Group - description: Scan policy owner group name + description: Scan policy owner group name. type: string - contextPath: TenableSC.ScanPolicy.Owner - description: Scan policy owner user name + description: Scan policy owner user name. type: string - contextPath: TenableSC.ScanPolicy.LastModified - description: Scan policy last modified time + description: Scan policy last modified time. type: date - contextPath: TenableSC.ScanPolicy.Type - description: Scan policy type + description: Scan policy type. type: string - arguments: - auto: PREDEFINED @@ -463,73 +463,73 @@ script: required: false secret: false deprecated: false - description: Get a list of Tenable.sc report definitions + description: Requires security manager authentication. Get a list of Tenable.sc report definitions. execution: false name: tenable-sc-list-report-definitions outputs: - contextPath: TenableSC.ReportDefinition.Name - description: Report definition name + description: Report definition name. type: string - contextPath: TenableSC.ReportDefinition.ID - description: Report definition ID + description: Report definition ID. type: number - contextPath: TenableSC.ReportDefinition.Description - description: Report definition description + description: Report definition description. type: string - contextPath: TenableSC.ReportDefinition.Type - description: Report definition type + description: Report definition type. type: string - contextPath: TenableSC.ReportDefinition.Group - description: Report definition owner group name + description: Report definition owner group name. type: string - contextPath: TenableSC.ReportDefinition.Owner - description: Report definition owner user name + description: Report definition owner user name. type: string - deprecated: false - description: Get a list of Tenable.sc scan repositories + description: Requires security manager authentication. Get a list of Tenable.sc scan repositories. execution: false name: tenable-sc-list-repositories outputs: - contextPath: TenableSC.ScanRepository.Name - description: Scan Repository name + description: Scan Repository name. type: string - contextPath: TenableSC.ScanRepository.ID - description: Scan Repository ID + description: Scan Repository ID. type: number - contextPath: TenableSC.ScanRepository.Description - description: Scan Repository + description: Scan Repository. type: string - deprecated: false - description: Get a list of Tenable.sc scan zones + description: Requires admin authentication. Get a list of Tenable.sc scan zones. execution: false name: tenable-sc-list-zones outputs: - contextPath: TenableSC.ScanZone.Name - description: Scan Zone name + description: Scan Zone name. type: string - contextPath: TenableSC.ScanZone.ID - description: Scan Zone ID + description: Scan Zone ID. type: number - contextPath: TenableSC.ScanZone.Description - description: Scan Zone description + description: Scan Zone description. type: string - contextPath: TenableSC.ScanZone.IPList - description: Scan Zone IP list + description: Scan Zone IP list. type: unknown - contextPath: TenableSC.ScanZone.ActiveScanners - description: Scan Zone active scanners + description: Scan Zone active scanners. type: number - contextPath: TenableSC.ScanZone.Scanner.Name - description: Scanner name + description: Scanner name. type: string - contextPath: TenableSC.ScanZone.Scanner.ID - description: Scanner ID + description: Scanner ID. type: number - contextPath: TenableSC.ScanZone.Scanner.Description - description: Scanner description + description: Scanner description. type: string - contextPath: TenableSC.ScanZone.Scanner.Status - description: Scanner status + description: Scanner status. type: number - arguments: - default: false @@ -712,38 +712,38 @@ script: deprecated: false description: Create a scan on Tenable.sc execution: false - name: tenable-sc-create-scan + name: Requires security manager authentication. tenable-sc-create-scan outputs: - contextPath: TenableSC.Scan.ID - description: Scan ID + description: Scan ID. type: string - contextPath: TenableSC.Scan.CreatorID - description: Scan's creator ID + description: Scan's creator ID. type: string - contextPath: TenableSC.Scan.Name - description: Scan Name + description: Scan Name. type: string - contextPath: TenableSC.Scan.Type - description: Scan type + description: Scan type. type: string - contextPath: TenableSC.Scan.CreatedTime - description: Scan creation time + description: Scan creation time. type: date - contextPath: TenableSC.Scan.OwnerName - description: Scan owner Username + description: Scan owner Username. type: string - contextPath: TenableSC.Scan.Reports - description: Scan report defintion IDs + description: Scan report definition IDs. type: unknown - arguments: - default: false - description: Scan ID, can be. retrieved from the list-scans command + description: Scan ID, can be. retrieved from the list-scans command. isArray: false name: scan_id required: true secret: false deprecated: false - description: Delete a scan in Tenable.sc + description: Requires security manager authentication. Delete a scan in Tenable.sc execution: false name: tenable-sc-delete-scan - arguments: @@ -759,67 +759,67 @@ script: required: false secret: false deprecated: false - description: Get a list of Tenable.sc Assets + description: Requires security manager authentication. Get a list of Tenable.sc Assets. execution: false name: tenable-sc-list-assets outputs: - contextPath: TenableSC.Asset.ID - description: Asset ID + description: Asset ID. type: string - contextPath: TenableSC.Asset.Name - description: Asset Name + description: Asset Name. type: string - contextPath: TenableSC.Asset.HostCount - description: Asset host IPs count + description: Asset host IPs count. type: number - contextPath: TenableSC.Asset.Type - description: Asset type + description: Asset type. type: string - contextPath: TenableSC.Asset.Tag - description: Asset tag + description: Asset tag. type: string - contextPath: TenableSC.Asset.Owner - description: Asset owner username + description: Asset owner username. type: string - contextPath: TenableSC.Asset.Group - description: Asset group + description: Asset group. type: string - contextPath: TenableSC.Asset.LastModified - description: Asset last modified time + description: Asset last modified time. type: date - arguments: - default: false - description: Asset Name + description: Asset Name. isArray: false name: name required: true secret: false - default: false - description: Asset description + description: Asset description. isArray: false name: description required: false secret: false - default: false - description: Asset owner ID, default is the Session User ID, can be retrieved from the list-users command + description: Asset owner ID, default is the Session User ID, can be retrieved from the list-users command. isArray: false name: owner_id required: false secret: false - default: false - description: 'Asset tag ' + description: Asset tag. isArray: true name: tag required: false secret: false - default: false - description: Comma separated list of IPs to include in the asset, e.g 10.0.0.2,10.0.0.4 + description: Comma separated list of IPs to include in the asset, e.g 10.0.0.2,10.0.0.4. isArray: false name: ip_list required: true secret: false deprecated: false - description: Create an Asset in Tenable.sc with provided IP addresses. + description: Requires security manager authentication. Create an Asset in Tenable.sc with provided IP addresses. execution: false name: tenable-sc-create-asset outputs: @@ -843,7 +843,7 @@ script: required: true secret: false deprecated: false - description: Get details for a given asset in Tenable.sc. + description: Requires security manager authentication. Get details for a given asset in Tenable.sc. execution: false name: tenable-sc-get-asset outputs: @@ -879,7 +879,7 @@ script: required: true secret: false deprecated: false - description: Delete the Asset with the given ID from Tenable.sc. + description: Requires security manager authentication. Delete the Asset with the given ID from Tenable.sc. execution: true name: tenable-sc-delete-asset - arguments: @@ -895,7 +895,7 @@ script: required: false secret: false deprecated: false - description: List alerts from Tenable.sc. + description: Requires security manager authentication. List alerts from Tenable.sc. execution: false name: tenable-sc-list-alerts outputs: @@ -934,7 +934,7 @@ script: required: true secret: false deprecated: false - description: Get information about a given alert in Tenable.sc. + description: Requires security manager authentication. Get information about a given alert in Tenable.sc. execution: false name: tenable-sc-get-alert outputs: @@ -991,7 +991,7 @@ script: required: false secret: false deprecated: false - description: Gets the specified device information. + description: Requires security manager authentication. Gets the specified device information. execution: false name: tenable-sc-get-device outputs: @@ -1072,7 +1072,7 @@ script: required: false secret: false deprecated: false - description: List users in Tenable.sc. + description: Results may vary based on the authentication type (admin or security manager). List users in Tenable.sc. execution: false name: tenable-sc-list-users outputs: @@ -1107,21 +1107,21 @@ script: description: User role name. type: string - deprecated: false - description: Retrieve licensing information from Tenable.sc + description: Requires admin authentication. Retrieve licensing information from Tenable.sc execution: false name: tenable-sc-get-system-licensing outputs: - contextPath: TenableSC.Status.ActiveIPS - description: Number of active IP addresses + description: Number of active IP addresses. type: number - contextPath: TenableSC.Status.LicensedIPS - description: Number of licensed IP addresses + description: Number of licensed IP addresses. type: Unknown - contextPath: TenableSC.Status.License description: License status. type: Unknown - deprecated: false - description: Get the system information and diagnostics from Tenable.sc. + description: Requires admin authentication. Get the system information and diagnostics from Tenable.sc. execution: false name: tenable-sc-get-system-information outputs: @@ -1156,9 +1156,7 @@ script: - auto: PREDEFINED default: false defaultValue: 'false' - description: |- - Filter only manageable alerts. By default, returns both usable and - manageable alerts. + description: Filter only manageable alerts. By default, returns both usable and manageable alerts. isArray: false name: manageable predefined: @@ -1181,7 +1179,7 @@ script: required: false secret: false deprecated: false - description: Returns all scan results in Tenable.sc. + description: Requires security manager authentication. Returns all scan results in Tenable.sc. execution: false name: tenable-sc-get-all-scan-results outputs: @@ -1247,7 +1245,7 @@ script: required: false secret: false deprecated: false - description: Demands sec man authentication. list all groups. + description: Requires security manager authentication. list all groups. execution: false name: tenable-sc-list-groups outputs: @@ -1436,7 +1434,7 @@ script: required: true secret: false deprecated: false - description: Creates a new user. + description: This command can be executed with both authentication types (admin or security manager) based on the roll_id you with to choose. Creates a new user. execution: false name: tenable-sc-create-user outputs: @@ -1898,7 +1896,7 @@ script: required: true secret: false deprecated: false - description: delete a user by given user_id. + description: This command can be executed with both authentication types (admin or security manager). Delete a user by given user_id. execution: false name: tenable-sc-delete-user outputs: @@ -1927,7 +1925,7 @@ script: required: false secret: false deprecated: false - description: update user details by given user_id. + description: Requires security manager authentication. list plugin families / return information about a plugin family given ID. execution: false name: tenable-sc-list-plugin-family outputs: @@ -2031,7 +2029,7 @@ script: - Ignore closed ports(aggressive) - Disabled(softer) deprecated: false - description: This command is prerequisite for creating remediation scan. creates policy. + description: Requires security manager authentication. This command is prerequisite for creating remediation scan. creates policy. execution: false name: tenable-sc-create-policy outputs: @@ -2185,7 +2183,7 @@ script: - ticket - user deprecated: false - description: Lists queries. + description: Requires security manager authentication. Lists queries. execution: false name: tenable-sc-list-query outputs: @@ -2573,7 +2571,7 @@ script: secret: false deprecated: false polling: true - description: Demands sec man authentication. Update an asset. + description: Requires security manager authentication. Update an asset. execution: false name: tenable-sc-update-asset outputs: @@ -2796,17 +2794,235 @@ script: secret: false defaultValue: nextDay deprecated: false - description: This command is prerequisite for creating remediation scan. creates policy. + description: Requires security manager authentication. This command is prerequisite for creating remediation scan. creates policy. execution: false name: tenable-sc-create-remediation-scan outputs: - - - - - - - + - contextPath: TenableSC.Scan.Assets + description: Scan assets. + type: Unknown + - contextPath: TenableSC.Scan.CanManage + description: Scan permissions. + type: String + - contextPath: TenableSC.Scan.CanUse + description: Scan permissions. + type: String + - contextPath: TenableSC.Scan.ClassifyMitigatedAge + description: Scan classify mitigated age. + type: String + - contextPath: TenableSC.Scan.CreatedTime + description: Scan creation time. + type: Date + - contextPath: TenableSC.Scan.Creator.Firstname + description: Scan creator first name. + type: String + - contextPath: TenableSC.Scan.Creator.ID + description: Scan creator ID. + type: String + - contextPath: TenableSC.Scan.Creator.Lastname + description: Scan creator last name. + type: String + - contextPath: TenableSC.Scan.Creator.Username + description: Scan creator user name. + type: String + - contextPath: TenableSC.Scan.Creator.UUID + description: Scan creator UUID. + type: String + - contextPath: TenableSC.Scan.Credentials + description: Scan credentials. + type: Unknown + - contextPath: TenableSC.Scan.Description + description: Scan description. + type: String + - contextPath: TenableSC.Scan.DhcpTracking + description: Scan DHCP tracking. + type: String + - contextPath: TenableSC.Scan.EmailOnFinish + description: Scan email on finish. + type: String + - contextPath: TenableSC.Scan.EmailOnLaunch + description: Scan email on launch. + type: String + - contextPath: TenableSC.Scan.ID + description: Scan ID. + type: String + - contextPath: TenableSC.Scan.IpList + description: Scan IP list. + type: String + - contextPath: TenableSC.Scan.MaxScanTime + description: Scan max scan time. + type: String + - contextPath: TenableSC.Scan.ModifiedTime + description: Scan last modification time. + type: Date + - contextPath: TenableSC.Scan.Name + description: Scan name. + type: String + - contextPath: TenableSC.Scan.NumDependents + description: Scan number of dependents. + type: Number + - contextPath: TenableSC.Scan.Owner.Firstname + description: Scan owner first name. + type: String + - contextPath: TenableSC.Scan.Owner.ID + description: Scan owner ID. + type: String + - contextPath: TenableSC.Scan.Owner.Lastname + description: Scan owner last name. + type: String + - contextPath: TenableSC.Scan.Owner.Username + description: Scan owner user name. + type: String + - contextPath: TenableSC.Scan.Owner.UUID + description: Scan owner UUID. + type: String + - contextPath: TenableSC.Scan.OwnerGroup.Description + description: Scan owner group description. + type: String + - contextPath: TenableSC.Scan.OwnerGroup.ID + description: Scan owner group ID. + type: String + - contextPath: TenableSC.Scan.OwnerGroup.Name + description: Scan owner group name. + type: String + - contextPath: TenableSC.Scan.Plugin.Description + description: Scan plugin description. + type: String + - contextPath: TenableSC.Scan.Plugin.ID + description: Scan plugin ID. + type: String + - contextPath: TenableSC.Scan.Plugin.Name + description: Scan plugin name. + type: String + - contextPath: TenableSC.Scan.Plugin.Type + description: Scan plugin type. + type: String + - contextPath: TenableSC.Scan.Policy.Context + description: Scan policy context. + type: String + - contextPath: TenableSC.Scan.Policy.Description + description: Scan policy description. + type: String + - contextPath: TenableSC.Scan.Policy.ID + description: Scan policy ID. + type: String + - contextPath: TenableSC.Scan.Policy.Name + description: Scan policy name. + type: String + - contextPath: TenableSC.Scan.Policy.Owner.Firstname + description: Scan policy owner first name. + type: String + - contextPath: TenableSC.Scan.Policy.Owner.ID + description: Scan policy owner ID. + type: String + - contextPath: TenableSC.Scan.Policy.Owner.Lastname + description: Scan policy owner last name. + type: String + - contextPath: TenableSC.Scan.Policy.Owner.Username + description: Scan policy owner user name. + type: String + - contextPath: TenableSC.Scan.Policy.Owner.UUID + description: Scan policy owner UUID. + type: String + - contextPath: TenableSC.Scan.Policy.OwnerGroup.Description + description: Scan policy owner group description. + type: String + - contextPath: TenableSC.Scan.Policy.OwnerGroup.ID + description: Scan policy owner group ID. + type: String + - contextPath: TenableSC.Scan.Policy.OwnerGroup.Name + description: Scan policy owner group name. + type: String + - contextPath: TenableSC.Scan.Policy.Tags + description: Scan policy tags + type: String + - contextPath: TenableSC.Scan.Policy.UUID + description: Scan policy UUID. + type: String + - contextPath: TenableSC.Scan.PolicyPrefs.Name + description: Scan policy preferation name. + type: String + - contextPath: TenableSC.Scan.PolicyPrefs.Value + description: Scan policy preferation value. + type: String + - contextPath: TenableSC.Scan.Reports + description: Scan reports. + type: Unknown + - contextPath: TenableSC.Scan.Repository.Description + description: Scan repository description. + type: String + - contextPath: TenableSC.Scan.Repository.ID + description: Scan repository ID. + type: String + - contextPath: TenableSC.Scan.Repository.Name + description: Scan repository name. + type: String + - contextPath: TenableSC.Scan.Repository.Type + description: Scan repository type. + type: String + - contextPath: TenableSC.Scan.Repository.UUID + description: Scan repository UUID. + type: String + - contextPath: TenableSC.Scan.RolloverType + description: Scan rollover type. + type: String + - contextPath: TenableSC.Scan.ScanResultID + description: Scan results ID. + type: String + - contextPath: TenableSC.Scan.ScanningVirtualHosts + description: Scan virtual hosts + type: String + - contextPath: TenableSC.Scan.Schedule.Dependent.Description + description: Scan schedule dependent description. + type: String + - contextPath: TenableSC.Scan.Schedule.Dependent.ID + description: Scan schedule dependent ID. + type: Number + - contextPath: TenableSC.Scan.Schedule.Dependent.Name + description: Scan schedule dependent name. + type: String + - contextPath: TenableSC.Scan.Schedule.Enabled + description: Scan schedule enabled. + type: String + - contextPath: TenableSC.Scan.Schedule.ID + description: Scan schedule ID. + type: Number + - contextPath: TenableSC.Scan.Schedule.NextRun + description: Scan schedule next run. + type: Number + - contextPath: TenableSC.Scan.Schedule.ObjectType + description: Scan schedule object type. + type: Number + - contextPath: TenableSC.Scan.Schedule.RepeatRule + description: Scan schedule repeat rule. + type: String + - contextPath: TenableSC.Scan.Schedule.Start + description: Scan schedule start time. + type: String + - contextPath: TenableSC.Scan.Schedule.Type + description: Scan schedule type. + type: String + - contextPath: TenableSC.Scan.Status + description: Scan status. + type: String + - contextPath: TenableSC.Scan.TimeoutAction + description: Scan timeout action. + type: String + - contextPath: TenableSC.Scan.Type + description: Scan type. + type: String + - contextPath: TenableSC.Scan.UUID + description: Scan UUID. + type: String + - contextPath: TenableSC.Scan.Zone.Description + description: Scan zone description. + type: String + - contextPath: TenableSC.Scan.Zone.ID + description: Scan zone ID. + type: Number + - contextPath: TenableSC.Scan.Zone.Name + description: Scan zone name. + type: String - arguments: - default: false description: The id of the scan we wish to get the report on. Can be retrieved from list-scans command. @@ -2840,14 +3056,67 @@ script: defaultValue: 10800 deprecated: false polling: true - description: Lists queries. + description: Requires security manager authentication. Polling command. Launch a scan by given scan ID, follow its status return a report when the scan is over. execution: false name: tenable-sc-launch-scan-report outputs: - - - - + - contextPath: TenableSC.ScanResults.ID + description: Scan results ID + type: number + - contextPath: TenableSC.ScanResults.Name + description: Scan name + type: string + - contextPath: TenableSC.ScanResults.Status + description: Scan status + type: string + - contextPath: TenableSC.ScanResults.ScannedIPs + description: Scan number of scanned IPs + type: number + - contextPath: TenableSC.ScanResults.StartTime + description: Scan start time + type: date + - contextPath: TenableSC.ScanResults.EndTime + description: Scan end time + type: date + - contextPath: TenableSC.ScanResults.Checks + description: Scan completed checks + type: number + - contextPath: TenableSC.ScanResults.RepositoryName + description: Scan repository name + type: string + - contextPath: TenableSC.ScanResults.Description + description: Scan description + type: string + - contextPath: TenableSC.ScanResults.Vulnerability.ID + description: Scan vulnerability ID + type: number + - contextPath: TenableSC.ScanResults.Vulnerability.Name + description: Scan vulnerability Name + type: string + - contextPath: TenableSC.ScanResults.Vulnerability.Family + description: Scan vulnerability family + type: string + - contextPath: TenableSC.ScanResults.Vulnerability.Severity + description: Scan vulnerability severity + type: string + - contextPath: TenableSC.ScanResults.Vulnerability.Total + description: Scan vulnerability total hosts + type: number + - contextPath: TenableSC.ScanResults.Policy + description: Scan policy + type: string + - contextPath: TenableSC.ScanResults.Group + description: Scan owner group name + type: string + - contextPath: TenableSC.ScanResults.Owner + description: Scan owner user name + type: string + - contextPath: TenableSC.ScanResults.Duration + description: Scan duration in minutes + type: number + - contextPath: TenableSC.ScanResults.ImportTime + description: Scan import time + type: date isfetch: true runonce: false script: '-' From c1bab9888901aaa108b9bd5521d1ca2cca68fc09 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Thu, 18 May 2023 17:10:59 +0300 Subject: [PATCH 027/115] validate fixes and added RN --- .../Integrations/Tenable_sc/Tenable_sc.yml | 8 ++++---- .../Playbooks/playbook-tenable.sc-scan.yml | 2 +- Packs/Tenable_sc/ReleaseNotes/1_0_10.md | 13 ++++++------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index d03cd904c29e..b6cb8ea0a43f 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -11,11 +11,11 @@ configuration: required: true type: 0 section: Connect -- display: access key +- display: Access key name: creds_keys required: false type: 9 - displaypassword: secret key + displaypassword: Secret key section: Connect - display: Username hiddenusername: true @@ -694,7 +694,7 @@ script: secret: false - default: false description: 'A comma-separated list of days of the week to run the schedule at. Possible values are: SU, MO, TU, WE, TH, FR, SA.' - isArray: yes + isArray: true name: repeat_rule_by_day required: false secret: false @@ -2704,7 +2704,7 @@ script: secret: false - default: false description: 'A comma-separated list of days of the week to run the schedule at. Possible values are: SU, MO, TU, WE, TH, FR, SA.' - isArray: yes + isArray: true name: repeat_rule_by_day required: false secret: false diff --git a/Packs/Tenable_sc/Playbooks/playbook-tenable.sc-scan.yml b/Packs/Tenable_sc/Playbooks/playbook-tenable.sc-scan.yml index 0888052f7e57..9c63bcb8c94f 100644 --- a/Packs/Tenable_sc/Playbooks/playbook-tenable.sc-scan.yml +++ b/Packs/Tenable_sc/Playbooks/playbook-tenable.sc-scan.yml @@ -1,7 +1,7 @@ id: tenable-sc-scan version: -1 name: Launch Scan - Tenable.sc -description: Deprecated. use tenable-sc-launch-scan-report command instead. +description: Deprecated. Use tenable-sc-launch-scan-report command instead. deprecated: true fromversion: 5.0.0 starttaskid: "0" diff --git a/Packs/Tenable_sc/ReleaseNotes/1_0_10.md b/Packs/Tenable_sc/ReleaseNotes/1_0_10.md index 7b4078e5e097..602e7eb6232b 100644 --- a/Packs/Tenable_sc/ReleaseNotes/1_0_10.md +++ b/Packs/Tenable_sc/ReleaseNotes/1_0_10.md @@ -1,12 +1,11 @@ - #### Integrations - ##### Tenable.sc - -- %%UPDATE_RN%% +- Added the **time_zone**, **start_time**, **repeat_rule_freq**, **repeat_rule_interval**, **repeat_rule_by_day**, **enabled** arguments to the **tenable-sc-create-scan** command. For more information, refer to the integration readme. +- Added the **query_id**, **sort_direction**, **sort_field**, **source_type** arguments to the **tenable-sc-get-vulnerability** command. For more information, refer to the integration readme. +- Improved implementation of the **tenable-sc-list-zones** command. +- Added the following commands: **tenable-sc-list-groups**, **tenable-sc-create-user**, **tenable-sc-update-user**, **tenable-sc-delete-user**, **tenable-sc-list-plugin-family**, **tenable-sc-create-policy**, **enable-sc-list-query**, **tenable-sc-launch-scan-report**, **tenable-sc-create-remediation-scan**, **tenable-sc-update-asset**. For more information, refer to the integration readme. +- Added the **Access key** and **Secret key** integration params to support a more secured instance configuration. pre-configured integration will not be affected by this change. #### Playbooks - ##### Launch Scan - Tenable.sc - -- Deprecated. Use %%% instead. +- Deprecated. Use **tenable-sc-launch-scan-report** instead. \ No newline at end of file From 5d33d773be4fa6bfbf4455ae86ec129fe69bfa38 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Thu, 18 May 2023 17:14:12 +0300 Subject: [PATCH 028/115] generated readme --- .../Integrations/Tenable_sc/README.md | 4851 +++++------------ 1 file changed, 1358 insertions(+), 3493 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/README.md b/Packs/Tenable_sc/Integrations/Tenable_sc/README.md index e519f5182354..e093b25de10e 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/README.md +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/README.md @@ -1,3493 +1,1358 @@ - -

Use the Tenable.sc integration to get a real-time, continuous assessment of your security posture so you can find and fix vulnerabilities faster.

-

All data in Tenable.sc is managed using group level permissions. If you have several groups, data (scans, scan results, assets, etc) can be viewable but not manageable. Users with Security Manager role  can manage everything. These permissions come into play when multiple groups are in use.

-

It is important to know what data is manageable for the user in order to work with the integration.

-

This integration was integrated and tested with Tenable.sc v5.7.0.

-

Use cases

-
    -
  • Create and run scans.
  • -
  • Launch and manage scan results and the found vulnerabilities.
  • -
  • Create and view assets.
  • -
  • View policies, repositories, credentials, users and more system information.
  • -
  • View and real-time receiving of alerts.
  • -
-

Tenable.sc Playbook

-

Tenable.sc - Launch scan

-


image

-

Configure tenable.sc on Cortex XSOAR

-

To use the Tenable.sc integration in Cortex XSOAR, a user with administrative privileges is recommended.

-
    -
  1. Navigate to Settings > Integrations > Servers & Services.
  2. -
  3. Search for Tenable.sc.
  4. -
  5. Click Add instance to create and configure a new integration instance.
    -
      -
    • -Name: a textual name for the integration instance.
    • -
    • Server URL (e.g. https://192.168.0.1)
    • -
    • Username
    • -
    • Trust any certificate (not secure)
    • -
    • Use system proxy settings
    • -
    • Fetch incidents
    • -
    • First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days, 3 months, 1 year):
    • -
    • Incident type
    • -
    -
  6. -
  7. Click Test to validate the URLs, token, and connection.
  8. -
-

Fetched Incidents Data

-

For the first fetch, you can specify the time range to return alerts for. Subsequent fetches return alerts from Tenable.sc according to their last triggered time.

-
[
-            {
-                "id": "1",
-                "name": "bwu_alert1",
-                "description": "",
-                "lastTriggered": "1485891841",
-                "triggerName": "sumip",
-                "triggerOperator": ">=",
-                "triggerValue": "5",
-                "action": [
-                    {
-                        "id": "1",
-                        "type": "ticket",
-                        "definition": {
-                            "assignee": {
-                                "id": "4",
-                                "username": "API17",
-                                "firstname": "API17",
-                                "lastname": ""
-                            },
-                            "name": "Ticket opened by alert",
-                            "description": "",
-                            "notes": ""
-                        },
-                        "status": "0",
-                        "users": [],
-                        "objectID": null
-                    }
-                ],
-                "query": {
-                    "id": "1648",
-                    "name": "Query for alert 'bwu_alert1' at 1463283903",
-                    "description": ""
-                },
-                "owner": {
-                    "id": "4",
-                    "username": "API17",
-                    "firstname": "API17",
-                    "lastname": ""
-                }
-            },
-            {
-                "id": "2",
-                "name": "Test Alert",
-                "description": "Maya test alert",
-                "lastTriggered": "1543248911",
-                "triggerName": "sumip",
-                "triggerOperator": ">=",
-                "triggerValue": "0",
-                "action": [
-                    {
-                        "id": "10",
-                        "type": "notification",
-                        "definition": {
-                            "message": "Event!",
-                            "users": [
-                                {
-                                    "id": "53",
-                                    "username": "API55",
-                                    "firstname": "API55",
-                                    "lastname": ""
-                                }
-                            ]
-                        },
-                        "status": "0",
-                        "users": [
-                            {
-                                "id": "53",
-                                "username": "API55",
-                                "firstname": "API55",
-                                "lastname": ""
-                            }
-                        ],
-                        "objectID": null
-                    },
-                    {
-                        "id": "11",
-                        "type": "ticket",
-                        "definition": {
-                            "assignee": {
-                                "id": "53",
-                                "username": "API55",
-                                "firstname": "API55",
-                                "lastname": ""
-                            },
-                            "name": "Ticket opened by alert",
-                            "description": "",
-                            "notes": ""
-                        },
-                        "status": "0",
-                        "users": [],
-                        "objectID": null
-                    }
-                ],
-                "query": {
-                    "id": "12669",
-                    "name": "IP Summary",
-                    "description": ""
-                },
-                "owner": {
-                    "id": "53",
-                    "username": "API55",
-                    "firstname": "API55",
-                    "lastname": ""
-                }
-            },
-            {
-                "id": "3",
-                "name": "Test fetch",
-                "description": "",
-                "lastTriggered": "0",
-                "triggerName": "sumport",
-                "triggerOperator": ">=",
-                "triggerValue": "1",
-                "action": [
-                    {
-                        "id": "5",
-                        "type": "ticket",
-                        "definition": {
-                            "assignee": {
-                                "id": "53",
-                                "username": "API55",
-                                "firstname": "API55",
-                                "lastname": ""
-                            },
-                            "name": "Ticket opened by alert",
-                            "description": "",
-                            "notes": ""
-                        },
-                        "status": "0",
-                        "users": [],
-                        "objectID": null
-                    }
-                ],
-                "query": {
-                    "id": "13177",
-                    "name": "IPv4 Fixed Address: 11.0.0.2",
-                    "description": ""
-                },
-                "owner": {
-                    "id": "53",
-                    "username": "API55",
-                    "firstname": "API55",
-                    "lastname": ""
-                }
-            }
-        ]
-
-

Commands

-

You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook. After you successfully execute a command, a DBot message appears in the War Room with the command details.

-
    -
  1. Get a list of scans: tenable-sc-list-scans
  2. -
  3. Initiate a scan: tenable-sc-launch-scan
  4. -
  5. Get vulnerability information for a scan: tenable-sc-get-vulnerability
  6. -
  7. Get the status of a scan: tenable-sc-get-scan-status
  8. -
  9. Get a report with scan results: tenable-sc-get-scan-report
  10. -
  11. Get a list of credentials: tenable-sc-list-credentials
  12. -
  13. Get a list of scan policies: tenable-sc-list-policies
  14. -
  15. Get a list of report definitions: tenable-sc-list-report-definitions
  16. -
  17. Get a list of scan repositories: tenable-sc-list-repositories
  18. -
  19. Get a list of scan zones: tenable-sc-list-zones
  20. -
  21. Create a scan: tenable-sc-create-scan
  22. -
  23. Delete a scan: tenable-sc-delete-scan
  24. -
  25. List all assets: tenable-sc-list-assets
  26. -
  27. Create an asset: tenable-sc-create-asset
  28. -
  29. Get asset information: tenable-sc-get-asset
  30. -
  31. Delete an asset: tenable-sc-delete-asset
  32. -
  33. Get a list of alerts: tenable-sc-list-alerts
  34. -
  35. Get alert information: tenable-sc-get-alert
  36. -
  37. Get device information for a user: tenable-sc-get-device
  38. -
  39. Get a list of users: tenable-sc-list-users
  40. -
  41. Get licensing information: tenable-sc-get-system-licensing
  42. -
  43. Get system information and diagnostics: tenable-sc-get-system-information
  44. -
  45. Get device information: tenable-sc-get-device
  46. -
  47. Get all scan results: tenable-sc-get-all-scan-results
  48. -
-

1. Get a list of scans

-
-

Returns a list of existing Tenable.sc scans.

-
Base Command
-

tenable-sc-list-scans

-
Input
- - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
manageableWhether to return only manageable scans. By default, returns both usable and manageable scans.Optional
-

 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.Scan.NamestringScan name.
TenableSC.Scan.IDnumberScan ID.
TenableSC.Scan.DescriptionstringScan description.
TenableSC.Scan.PolicystringScan policy name.
TenableSC.Scan.GroupstringScan policy owner group name.
TenableSC.Scan.OwnerstringScan policy owner user name.
-

 

-
Command Example
-
  !tenable-sc-list-scans manageable=true
-
Context Example
-
{
-    "TenableSC": {
-        "Scan": [
-            {
-                "Group": "Full Access",
-                "ID": "701",
-                "Name": "Test55",
-                "Owner": "API55",
-                "Policy": "Basic Discovery Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "702",
-                "Name": "Test55_2",
-                "Owner": "API55",
-                "Policy": "Full Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "703",
-                "Name": "test55_3",
-                "Owner": "API55",
-                "Policy": "Full Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1266",
-                "Name": "my_test",
-                "Owner": "API55",
-                "Policy": "Basic Discovery Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1267",
-                "Name": "my_test",
-                "Owner": "API55",
-                "Policy": "Basic Discovery Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1270",
-                "Name": "test5",
-                "Owner": "API55",
-                "Policy": "Basic Discovery Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1271",
-                "Name": "my_test",
-                "Owner": "API55",
-                "Policy": "Basic Discovery Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1274",
-                "Name": "sfsa",
-                "Owner": "API55",
-                "Policy": "Basic_Disc"
-            },
-            {
-                "Description": "desc",
-                "Group": "Full Access",
-                "ID": "1275",
-                "Name": "my_test_scan",
-                "Owner": "API55",
-                "Policy": "Basic Discovery Scan"
-            },
-            {
-                "Description": "desc",
-                "Group": "Full Access",
-                "ID": "1276",
-                "Name": "my_test_scan_plug",
-                "Owner": "API55",
-                "Policy": "Basic Network Scan"
-            },
-        
-        ]
-    }
-}
-
-
Human Readable Output
-

image

-

2. Initiate a scan

-
-

Launches an existing scan from Tenable.sc.

-
Base Command
-

tenable-sc-launch-scan

-
Input
- - - - - - - - - - - - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
scan_idScan ID (can be retrieved from the tenable-sc-list-scans command).Required
diagnostic_targetValid IP/hostname of a specific target to scan. Must be provided with diagnosticPassword.Optional
diagnostic_passwordNon empty string password.Optional
-

 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.ScanResults.NamestringScan name.
TenableSC.ScanResults.IDstringScan Results ID.
TenableSC.ScanResults.OwnerIDstringScan owner ID.
TenableSC.ScanResults.JobIDstringJob ID.
TenableSC.ScanResults.StatusstringScan status.
-

 

-
Command Example
-
!tenable-sc-launch-scan scan_id=1275 diagnostic_target=10.0.0.1 diagnostic_password=mypass
-
Context Example
-
{
-    "TenableSC": {
-        "ScanResults": {
-            "ID": "3398",
-            "JobID": "949739",
-            "Name": "my_test_scan",
-            "OwnerID": "53",
-            "Status": "Queued"
-        }
-    }
-}
-
-
Human Readable Output
-

image

-

3. Get vulnerability information for a scan

-
-

Returns details about a vulnerability from a specified Tenable.sc scan.

-
Base Command
-

tenable-sc-get-vulnerability

-
Input
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
vulnerability_idVulnerability ID from the scan-report command.Required
scan_results_idScan results ID from the scan-report command.Required
limitThe number of objects to return in one response (maximum limit is 200).Optional
pageThe page to return starting from 0.Optional
-

 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.ScanResults.IDnumberScan results ID.
TenableSC.ScanResults.Vulnerability.IDnumberVulnerability plugin ID.
TenableSC.ScanResults.Vulnerability.NamestringVulnerability name.
TenableSC.ScanResults.Vulnerability.DescriptionstringVulnerability description.
TenableSC.ScanResults.Vulnerability.TypestringVulnerability type.
TenableSC.ScanResults.Vulnerability.SeveritystringVulnerability Severity.
TenableSC.ScanResults.Vulnerability.SynopsisstringVulnerability Synopsis.
TenableSC.ScanResults.Vulnerability.SolutionstringVulnerability Solution.
TenableSC.ScanResults.Vulnerability.PublisheddateVulnerability publish date.
TenableSC.ScanResults.Vulnerability.CPEstringVulnerability CPE.
TenableSC.ScanResults.Vulnerability.CVEunknownVulnerability CVE.
TenableSC.ScanResults.Vulnerability.ExploitAvailablebooleanVulnerability exploit available.
TenableSC.ScanResults.Vulnerability.ExploitEasestringVulnerability exploit ease.
TenableSC.ScanResults.Vulnerability.RiskFactorstringVulnerability risk factor.
TenableSC.ScanResults.Vulnerability.CVSSBaseScorenumberVulnerability CVSS base score.
TenableSC.ScanResults.Vulnerability.CVSSTemporalScorenumberVulnerability CVSS temporal score.
TenableSC.ScanResults.Vulnerability.CVSSVectorstringVulnerability CVSS vector.
TenableSC.ScanResults.Vulnerability.PluginDetailsunknownVulnerability plugin details.
CVE.IDunknownCVE ID.
TenableSC.ScanResults.Vulnerability.Host.IPstringVulnerability Host IP.
TenableSC.ScanResults.Vulnerability.Host.MACstringVulnerability Host MAC.
TenableSC.ScanResults.Vulnerability.Host.PortnumberVulnerability Host Port.
TenableSC.ScanResults.Vulnerability.Host.ProtocolstringVulnerability Host Protocol.
-

 

-
Command Example
-
!tenable-sc-get-vulnerability scan_results_id=3331 vulnerability_id=117672
-
Context Example
-
{
-    "CVE": [
-        {
-            "ID": "CVE-2018-7584"
-        },
-        {
-            "ID": "CVE-2018-0737"
-        },
-        {
-            "ID": "CVE-2018-10546"
-        },
-        {
-            "ID": "CVE-2018-10547"
-        },
-        {
-            "ID": "CVE-2018-10548"
-        },
-        {
-            "ID": "CVE-2018-10549"
-        },
-        {
-            "ID": "CVE-2018-10545"
-        },
-        {
-            "ID": "CVE-2018-0732"
-        },
-        {
-            "ID": "CVE-2018-14851"
-        },
-        {
-            "ID": "CVE-2018-14883"
-        },
-        {
-            "ID": "CVE-2018-15132"
-        }
-    ],
-    "TenableSC": {
-        "ScanResults": {
-            "ID": "3331",
-            "Vulnerability": {
-                "CPE": "cpe:/a:tenable:securitycenter",
-                "CVE": [
-                    "CVE-2018-7584",
-                    "CVE-2018-0737",
-                    "CVE-2018-10546",
-                    "CVE-2018-10547",
-                    "CVE-2018-10548",
-                    "CVE-2018-10549",
-                    "CVE-2018-10545",
-                    "CVE-2018-0732",
-                    "CVE-2018-14851",
-                    "CVE-2018-14883",
-                    "CVE-2018-15132"
-                ],
-                "CVSSBaseScore": "7.5",
-                "CVSSTemporalScore": null,
-                "CVSSVector": "AV:N/AC:L/Au:N/C:P/I:P/A:P",
-                "Description": "According to its self-reported version, the Tenable SecurityCenter application installed on the remote host is prior to 5.7.1. It is, therefore, affected by multiple vulnerabilities.\n\nNote that Nessus has not tested for these issues but has instead relied only on the application's self-reported version number.",
-                "ExploitAvailable": "false",
-                "ExploitEase": "",
-                "ID": "117672",
-                "Name": "Tenable SecurityCenter \u003c 5.7.1 Multiple Vulnerabilities (TNS-2018-12)",
-                "PluginDetails": {
-                    "CheckType": "combined",
-                    "Family": "Misc.",
-                    "Modified": "2018-11-15T12:00:00Z",
-                    "Published": "2018-09-24T12:00:00Z"
-                },
-                "Published": "2018-09-17T12:00:00Z",
-                "RiskFactor": "High",
-                "Severity": "High",
-                "Solution": "Upgrade to Tenable SecurityCenter version 5.7.1 or later.",
-                "Synopsis": "An application installed on the remote host is affected by multiple vulnerabilities.",
-                "Type": "active"
-            }
-        }
-    }
-}
-
-
Human Readable Output
-

-

4. Get the status of a scan

-

Returns the status of a specified scan in Tenable.sc.

-
Base Command
-

tenable-sc-get-scan-status

-
Input
- - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
scan_results_idScan results ID from the tenable-sc-launch-scan command.Required
-

 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.ScanResults.StatusstringScan status.
TenableSC.ScanResults.NamestringScan name.
TenableSC.ScanResults.DescriptionunknownScan description.
TenableSC.ScanResults.IDunknownScan results ID.
-

 

-
Command Example
-
!tenable-sc-get-scan-status scan_results_id=3331
-
Context Example
-
{
-    "TenableSC": {
-        "ScanResults": {
-            "ID": "3331",
-            "Name": "中文scan",
-            "Status": "Completed"
-        }
-    }
-}
-
-
Human Readable Output
-

image

-

5. Get a report with scan results

-
-

Returns a single report with a Tenable.sc scan results.

-
Base Command
-

tenable-sc-get-scan-report

-
Input
- - - - - - - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
scan_results_idScan results ID.Required
vulnerability_severityComma-separated list of severity values of vulnerabilities to retrieve.Optional
-

 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.ScanResults.IDnumberScan results ID.
TenableSC.ScanResults.NamestringScan name.
TenableSC.ScanResults.StatusstringScan status.
TenableSC.ScanResults.ScannedIPsnumberScan number of scanned IPs.
TenableSC.ScanResults.StartTimedateScan start time.
TenableSC.ScanResults.EndTimedateScan end time.
TenableSC.ScanResults.ChecksnumberScan completed checks.
TenableSC.ScanResults.RepositoryNamestringScan repository name.
TenableSC.ScanResults.DescriptionstringScan description.
TenableSC.ScanResults.Vulnerability.IDnumberScan vulnerability ID.
TenableSC.ScanResults.Vulnerability.NamestringScan vulnerability Name.
TenableSC.ScanResults.Vulnerability.FamilystringScan vulnerability family.
TenableSC.ScanResults.Vulnerability.SeveritystringScan vulnerability severity.
TenableSC.ScanResults.Vulnerability.TotalnumberScan vulnerability total hosts.
TenableSC.ScanResults.PolicystringScan policy.
TenableSC.ScanResults.GroupstringScan owner group name.
TenableSC.ScanResults.OwnerstringScan owner user name.
TenableSC.ScanResults.DurationnumberScan duration in minutes.
TenableSC.ScanResults.ImportTimedateScan import time.
-

 

-
Command Example
-
  !tenable-sc-get-scan-report scan_results_id=3331 vulnerability_severity=High
-
-
Context Example
-
{
-    "TenableSC": {
-        "ScanResults": {
-            "Checks": "17155624",
-            "Duration": 97.13333333333334,
-            "EndTime": "2018-11-20T17:37:11Z",
-            "Group": "Full Access",
-            "ID": "3331",
-            "ImportTime": "2018-11-20T17:37:15Z",
-            "Name": "中文scan",
-            "Owner": "API17",
-            "Policy": "Basic Network Scan",
-            "RepositoryName": "repo",
-            "ScannedIPs": "172",
-            "StartTime": "2018-11-20T16:00:03Z",
-            "Status": "Completed",
-            "Vulnerability": [
-                {
-                    "Description": "An update for bind is now available for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having a security impact of Important. A Common Vulnerability Scoring System (CVSS) base score, which gives a detailed severity rating, is available for each vulnerability from the CVE link(s) in the References section.\n\nThe Berkeley Internet Name Domain (BIND) is an implementation of the Domain Name System (DNS) protocols. BIND includes a DNS server (named); a resolver library (routines for applications to use when interfacing with DNS); and tools for verifying that the DNS server is operating correctly.\n\nSecurity Fix(es) :\n\n* A use-after-free flaw leading to denial of service was found in the way BIND internally handled cleanup operations on upstream recursion fetch contexts. A remote attacker could potentially use this flaw to make named, acting as a DNSSEC validating resolver, exit unexpectedly with an assertion failure via a specially crafted DNS request.\n(CVE-2017-3145)\n\nRed Hat would like to thank ISC for reporting this issue. Upstream acknowledges Jayachandran Palanisamy (Cygate AB) as the original reporter.",
-                    "Family": "CentOS Local Security Checks",
-                    "ID": "106234",
-                    "Name": "CentOS 7 : bind (CESA-2018:0102)",
-                    "Severity": "High",
-                    "Total": "1"
-                },
-                {
-                    "Description": "An update for kernel is now available for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having a security impact of Important. A Common Vulnerability Scoring System (CVSS) base score, which gives a detailed severity rating, is available for each vulnerability from the CVE link(s) in the References section.\n\nThe kernel packages contain the Linux kernel, the core of any Linux operating system.\n\nSecurity Fix(es) :\n\nAn industry-wide issue was found in the way many modern microprocessor designs have implemented speculative execution of instructions (a commonly used performance optimization). There are three primary variants of the issue which differ in the way the speculative execution can be exploited.\n\nNote: This issue is present in hardware and cannot be fully fixed via software update. The updated kernel packages provide software mitigation for this hardware issue at a cost of potential performance penalty. Please refer to References section for further information about this issue and the performance impact.\n\nIn this update initial mitigations for IBM Power (PowerPC) and IBM zSeries (S390) architectures are provided.\n\n* Variant CVE-2017-5715 triggers the speculative execution by utilizing branch target injection. It relies on the presence of a precisely-defined instruction sequence in the privileged code as well as the fact that memory accesses may cause allocation into the microprocessor's data cache even for speculatively executed instructions that never actually commit (retire). As a result, an unprivileged attacker could use this flaw to cross the syscall and guest/host boundaries and read privileged memory by conducting targeted cache side-channel attacks. This fix specifically addresses S390 processors. (CVE-2017-5715, Important)\n\n* Variant CVE-2017-5753 triggers the speculative execution by performing a bounds-check bypass. It relies on the presence of a precisely-defined instruction sequence in the privileged code as well as the fact that memory accesses may cause allocation into the microprocessor's data cache even for speculatively executed instructions that never actually commit (retire). As a result, an unprivileged attacker could use this flaw to cross the syscall boundary and read privileged memory by conducting targeted cache side-channel attacks. This fix specifically addresses S390 and PowerPC processors. (CVE-2017-5753, Important)\n\n* Variant CVE-2017-5754 relies on the fact that, on impacted microprocessors, during speculative execution of instruction permission faults, exception generation triggered by a faulting access is suppressed until the retirement of the whole instruction block. In a combination with the fact that memory accesses may populate the cache even when the block is being dropped and never committed (executed), an unprivileged local attacker could use this flaw to read privileged (kernel space) memory by conducting targeted cache side-channel attacks. Note: CVE-2017-5754 affects Intel x86-64 microprocessors. AMD x86-64 microprocessors are not affected by this issue. This fix specifically addresses PowerPC processors.\n(CVE-2017-5754, Important)\n\nRed Hat would like to thank Google Project Zero for reporting CVE-2017-5715, CVE-2017-5753, and CVE-2017-5754.\n\nThis update also fixes the following security issues and bugs :\n\nSpace precludes documenting all of the bug fixes and enhancements included in this advisory. To see the complete list of bug fixes and enhancements, refer to the following KnowledgeBase article:\nhttps://access.redhat.com/articles/ 3327131.",
-                    "Family": "CentOS Local Security Checks",
-                    "ID": "106353",
-                    "Name": "CentOS 7 : kernel (CESA-2018:0151) (Meltdown) (Spectre)",
-                    "Severity": "High",
-                    "Total": "1"
-                },
-                {
-                    "Description": "An update for dhcp is now available for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having a security impact of Important. A Common Vulnerability Scoring System (CVSS) base score, which gives a detailed severity rating, is available for each vulnerability from the CVE link(s) in the References section.\n\nThe Dynamic Host Configuration Protocol (DHCP) is a protocol that allows individual devices on an IP network to get their own network configuration information, including an IP address, a subnet mask, and a broadcast address. The dhcp packages provide a relay agent and ISC DHCP service required to enable and administer DHCP on a network.\n\nSecurity Fix(es) :\n\n* dhcp: Buffer overflow in dhclient possibly allowing code execution triggered by malicious server (CVE-2018-5732)\n\n* dhcp: Reference count overflow in dhcpd allows denial of service (CVE-2018-5733)\n\nFor more details about the security issue(s), including the impact, a CVSS score, and other related information, refer to the CVE page(s) listed in the References section.\n\nRed Hat would like to thank ISC for reporting these issues. Upstream acknowledges Felix Wilhelm (Google) as the original reporter of these issues.",
-                    "Family": "CentOS Local Security Checks",
-                    "ID": "108338",
-                    "Name": "CentOS 7 : dhcp (CESA-2018:0483)",
-                    "Severity": "High",
-                    "Total": "1"
-                },
-                {
-                    "Description": "An update for glibc is now available for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having a security impact of Moderate. A Common Vulnerability Scoring System (CVSS) base score, which gives a detailed severity rating, is available for each vulnerability from the CVE link(s) in the References section.\n\nThe glibc packages provide the standard C libraries (libc), POSIX thread libraries (libpthread), standard math libraries (libm), and the name service cache daemon (nscd) used by multiple programs on the system. Without these libraries, the Linux system cannot function correctly.\n\nSecurity Fix(es) :\n\n* glibc: realpath() buffer underflow when getcwd() returns relative path allows privilege escalation (CVE-2018-1000001)\n\n* glibc: Buffer overflow in glob with GLOB_TILDE (CVE-2017-15670)\n\n* glibc: Buffer overflow during unescaping of user names with the ~ operator (CVE-2017-15804)\n\n* glibc: denial of service in getnetbyname function (CVE-2014-9402)\n\n* glibc: DNS resolver NULL pointer dereference with crafted record type (CVE-2015-5180)\n\n* glibc: Fragmentation attacks possible when EDNS0 is enabled (CVE-2017-12132)\n\nFor more details about the security issue(s), including the impact, a CVSS score, and other related information, refer to the CVE page(s) listed in the References section.\n\nRed Hat would like to thank halfdog for reporting CVE-2018-1000001.\nThe CVE-2015-5180 issue was discovered by Florian Weimer (Red Hat Product Security).\n\nAdditional Changes :\n\nFor detailed information on changes in this release, see the Red Hat Enterprise Linux 7.5 Release Notes linked from the References section.",
-                    "Family": "CentOS Local Security Checks",
-                    "ID": "109371",
-                    "Name": "CentOS 7 : glibc (CESA-2018:0805)",
-                    "Severity": "High",
-                    "Total": "1"
-                },
-                {
-                    "Description": "An update for dhcp is now available for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having a security impact of Critical. A Common Vulnerability Scoring System (CVSS) base score, which gives a detailed severity rating, is available for each vulnerability from the CVE link(s) in the References section.\n\nThe Dynamic Host Configuration Protocol (DHCP) is a protocol that allows individual devices on an IP network to get their own network configuration information, including an IP address, a subnet mask, and a broadcast address. The dhcp packages provide a relay agent and ISC DHCP service required to enable and administer DHCP on a network.\n\nSecurity Fix(es) :\n\n* A command injection flaw was found in the NetworkManager integration script included in the DHCP client packages in Red Hat Enterprise Linux. A malicious DHCP server, or an attacker on the local network able to spoof DHCP responses, could use this flaw to execute arbitrary commands with root privileges on systems using NetworkManager and configured to obtain network configuration using the DHCP protocol.\n(CVE-2018-1111)\n\nRed Hat would like to thank Felix Wilhelm (Google Security Team) for reporting this issue.",
-                    "Family": "CentOS Local Security Checks",
-                    "ID": "109814",
-                    "Name": "CentOS 7 : dhcp (CESA-2018:1453)",
-                    "Severity": "High",
-                    "Total": "1"
-                },
-                {
-                    "Description": "An update for procps-ng is now available for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having a security impact of Important. A Common Vulnerability Scoring System (CVSS) base score, which gives a detailed severity rating, is available for each vulnerability from the CVE link(s) in the References section.\n\nThe procps-ng packages contain a set of system utilities that provide system information, including ps, free, skill, pkill, pgrep, snice, tload, top, uptime, vmstat, w, watch, and pwdx.\n\nSecurity Fix(es) :\n\n* procps-ng, procps: Integer overflows leading to heap overflow in file2strvec (CVE-2018-1124)\n\n* procps-ng, procps: incorrect integer size in proc/alloc.* leading to truncation / integer overflow issues (CVE-2018-1126)\n\nFor more details about the security issue(s), including the impact, a CVSS score, and other related information, refer to the CVE page(s) listed in the References section.\n\nRed Hat would like to thank Qualys Research Labs for reporting these issues.",
-                    "Family": "CentOS Local Security Checks",
-                    "ID": "110204",
-                    "Name": "CentOS 7 : procps-ng (CESA-2018:1700)",
-                    "Severity": "High",
-                    "Total": "1"
-                },
-                {
-                    "Description": "An update for kernel is now available for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having a security impact of Important. A Common Vulnerability Scoring System (CVSS) base score, which gives a detailed severity rating, is available for each vulnerability from the CVE link(s) in the References section.\n\nThe kernel packages contain the Linux kernel, the core of any Linux operating system.\n\nSecurity Fix(es) :\n\n* Kernel: KVM: error in exception handling leads to wrong debug stack value (CVE-2018-1087)\n\n* Kernel: error in exception handling leads to DoS (CVE-2018-8897)\n\n* Kernel: ipsec: xfrm: use-after-free leading to potential privilege escalation (CVE-2017-16939)\n\n* kernel: Out-of-bounds write via userland offsets in ebt_entry struct in netfilter/ebtables.c (CVE-2018-1068)\n\n* kernel: ptrace() incorrect error handling leads to corruption and DoS (CVE-2018-1000199)\n\n* kernel: guest kernel crash during core dump on POWER9 host (CVE-2018-1091)\n\nFor more details about the security issue(s), including the impact, a CVSS score, and other related information, refer to the CVE page(s) listed in the References section.\n\nRed Hat would like to thank Andy Lutomirski for reporting CVE-2018-1087 and CVE-2018-1000199 and Nick Peterson (Everdox Tech LLC) and Andy Lutomirski for reporting CVE-2018-8897.\n\nBug Fix(es) :\n\nThese updated kernel packages include also numerous bug fixes. Space precludes documenting all of these bug fixes in this advisory. See the bug fix descriptions in the related Knowledge Article:\nhttps://access.redhat.com/ articles/3431641",
-                    "Family": "CentOS Local Security Checks",
-                    "ID": "110245",
-                    "Name": "CentOS 7 : kernel (CESA-2018:1318)",
-                    "Severity": "High",
-                    "Total": "1"
-                },
-                {
-                    "Description": "An update for yum-utils is now available for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having a security impact of Important. A Common Vulnerability Scoring System (CVSS) base score, which gives a detailed severity rating, is available for each vulnerability from the CVE link(s) in the References section.\n\nThe yum-utils packages provide a collection of utilities and examples for the yum package manager to make yum easier and more powerful to use.\n\nSecurity Fix(es) :\n\n* yum-utils: reposync: improper path validation may lead to directory traversal (CVE-2018-10897)\n\nFor more details about the security issue(s), including the impact, a CVSS score, and other related information, refer to the CVE page(s) listed in the References section.\n\nRed Hat would like to thank Jay Grizzard (Clover Network) and Aaron Levy (Clover Network) for reporting this issue.",
-                    "Family": "CentOS Local Security Checks",
-                    "ID": "111615",
-                    "Name": "CentOS 7 : yum-utils (CESA-2018:2285)",
-                    "Severity": "High",
-                    "Total": "1"
-                },
-                {
-                    "Description": "An update for kernel is now available for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having a security impact of Important. A Common Vulnerability Scoring System (CVSS) base score, which gives a detailed severity rating, is available for each vulnerability from the CVE link(s) in the References section.\n\nThe kernel packages contain the Linux kernel, the core of any Linux operating system.\n\nSecurity Fix(es) :\n\n* Modern operating systems implement virtualization of physical memory to efficiently use available system resources and provide inter-domain protection through access control and isolation. The L1TF issue was found in the way the x86 microprocessor designs have implemented speculative execution of instructions (a commonly used performance optimisation) in combination with handling of page-faults caused by terminated virtual to physical address resolving process. As a result, an unprivileged attacker could use this flaw to read privileged memory of the kernel or other processes and/or cross guest/host boundaries to read host memory by conducting targeted cache side-channel attacks.\n(CVE-2018-3620, CVE-2018-3646)\n\n* An industry-wide issue was found in the way many modern microprocessor designs have implemented speculative execution of instructions past bounds check. The flaw relies on the presence of a precisely-defined instruction sequence in the privileged code and the fact that memory writes occur to an address which depends on the untrusted value. Such writes cause an update into the microprocessor's data cache even for speculatively executed instructions that never actually commit (retire). As a result, an unprivileged attacker could use this flaw to influence speculative execution and/or read privileged memory by conducting targeted cache side-channel attacks.\n(CVE-2018-3693)\n\n* A flaw named SegmentSmack was found in the way the Linux kernel handled specially crafted TCP packets. A remote attacker could use this flaw to trigger time and calculation expensive calls to tcp_collapse_ofo_queue() and tcp_prune_ofo_queue() functions by sending specially modified packets within ongoing TCP sessions which could lead to a CPU saturation and hence a denial of service on the system. Maintaining the denial of service condition requires continuous two-way TCP sessions to a reachable open port, thus the attacks cannot be performed using spoofed IP addresses.\n(CVE-2018-5390)\n\n* kernel: crypto: privilege escalation in skcipher_recvmsg function (CVE-2017-13215)\n\n* kernel: mm: use-after-free in do_get_mempolicy function allows local DoS or other unspecified impact (CVE-2018-10675)\n\n* kernel: race condition in snd_seq_write() may lead to UAF or OOB access (CVE-2018-7566)\n\nFor more details about the security issue(s), including the impact, a CVSS score, and other related information, refer to the CVE page(s) listed in the References section.\n\nRed Hat would like to thank Intel OSSIRT (Intel.com) for reporting CVE-2018-3620 and CVE-2018-3646; Vladimir Kiriansky (MIT) and Carl Waldspurger (Carl Waldspurger Consulting) for reporting CVE-2018-3693;\nand Juha-Matti Tilli (Aalto University, Department of Communications and Networking and Nokia Bell Labs) for reporting CVE-2018-5390.\n\nBug Fix(es) :\n\nThese updated kernel packages include also numerous bug fixes. Space precludes documenting all of the bug fixes in this advisory. See the descriptions in the related Knowledge Article :\n\nhttps://access.redhat.com/articles/3527791",
-                    "Family": "CentOS Local Security Checks",
-                    "ID": "111703",
-                    "Name": "CentOS 7 : kernel (CESA-2018:2384) (Foreshadow)",
-                    "Severity": "High",
-                    "Total": "1"
-                },
-                {
-                    "Description": "An update for mariadb is now available for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having a security impact of Moderate. A Common Vulnerability Scoring System (CVSS) base score, which gives a detailed severity rating, is available for each vulnerability from the CVE link(s) in the References section.\n\nMariaDB is a multi-user, multi-threaded SQL database server that is binary compatible with MySQL.\n\nThe following packages have been upgraded to a later upstream version:\nmariadb (5.5.60). (BZ#1584668, BZ#1584671, BZ#1584674, BZ#1601085)\n\nSecurity Fix(es) :\n\n* mysql: Client programs unspecified vulnerability (CPU Jul 2017) (CVE-2017-3636)\n\n* mysql: Server: DML unspecified vulnerability (CPU Jul 2017) (CVE-2017-3641)\n\n* mysql: Client mysqldump unspecified vulnerability (CPU Jul 2017) (CVE-2017-3651)\n\n* mysql: Server: Replication unspecified vulnerability (CPU Oct 2017) (CVE-2017-10268)\n\n* mysql: Server: Optimizer unspecified vulnerability (CPU Oct 2017) (CVE-2017-10378)\n\n* mysql: Client programs unspecified vulnerability (CPU Oct 2017) (CVE-2017-10379)\n\n* mysql: Server: DDL unspecified vulnerability (CPU Oct 2017) (CVE-2017-10384)\n\n* mysql: Server: Partition unspecified vulnerability (CPU Jan 2018) (CVE-2018-2562)\n\n* mysql: Server: DDL unspecified vulnerability (CPU Jan 2018) (CVE-2018-2622)\n\n* mysql: Server: Optimizer unspecified vulnerability (CPU Jan 2018) (CVE-2018-2640)\n\n* mysql: Server: Optimizer unspecified vulnerability (CPU Jan 2018) (CVE-2018-2665)\n\n* mysql: Server: Optimizer unspecified vulnerability (CPU Jan 2018) (CVE-2018-2668)\n\n* mysql: Server: Replication unspecified vulnerability (CPU Apr 2018) (CVE-2018-2755)\n\n* mysql: Client programs unspecified vulnerability (CPU Apr 2018) (CVE-2018-2761)\n\n* mysql: Server: Locking unspecified vulnerability (CPU Apr 2018) (CVE-2018-2771)\n\n* mysql: Server: Optimizer unspecified vulnerability (CPU Apr 2018) (CVE-2018-2781)\n\n* mysql: Server: DDL unspecified vulnerability (CPU Apr 2018) (CVE-2018-2813)\n\n* mysql: Server: DDL unspecified vulnerability (CPU Apr 2018) (CVE-2018-2817)\n\n* mysql: InnoDB unspecified vulnerability (CPU Apr 2018) (CVE-2018-2819)\n\n* mysql: Server: DDL unspecified vulnerability (CPU Jul 2017) (CVE-2017-3653)\n\n* mysql: use of SSL/TLS not enforced in libmysqld (Return of BACKRONYM) (CVE-2018-2767)\n\nFor more details about the security issue(s), including the impact, a CVSS score, and other related information, refer to the CVE page(s) listed in the References section.\n\nBug Fix(es) :\n\n* Previously, the mysqladmin tool waited for an inadequate length of time if the socket it listened on did not respond in a specific way.\nConsequently, when the socket was used while the MariaDB server was starting, the mariadb service became unresponsive for a long time.\nWith this update, the mysqladmin timeout has been shortened to 2 seconds. As a result, the mariadb service either starts or fails but no longer hangs in the described situation. (BZ#1584023)",
-                    "Family": "CentOS Local Security Checks",
-                    "ID": "112020",
-                    "Name": "CentOS 7 : mariadb (CESA-2018:2439)",
-                    "Severity": "High",
-                    "Total": "1"
-                },
-                {
-                    "Description": "An update for bind is now available for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having a security impact of Important. A Common Vulnerability Scoring System (CVSS) base score, which gives a detailed severity rating, is available for each vulnerability from the CVE link(s) in the References section.\n\nThe Berkeley Internet Name Domain (BIND) is an implementation of the Domain Name System (DNS) protocols. BIND includes a DNS server (named); a resolver library (routines for applications to use when interfacing with DNS); and tools for verifying that the DNS server is operating correctly.\n\nSecurity Fix(es) :\n\n* bind: processing of certain records when 'deny-answer-aliases' is in use may trigger an assert leading to a denial of service (CVE-2018-5740)\n\nFor more details about the security issue(s), including the impact, a CVSS score, and other related information, refer to the CVE page(s) listed in the References section.\n\nRed Hat would like to thank ISC for reporting this issue. Upstream acknowledges Tony Finch (University of Cambridge) as the original reporter.",
-                    "Family": "CentOS Local Security Checks",
-                    "ID": "112164",
-                    "Name": "CentOS 7 : bind (CESA-2018:2570)",
-                    "Severity": "High",
-                    "Total": "1"
-                },
-                {
-                    "Description": "According to its self-reported version, the Tenable SecurityCenter application installed on the remote host is prior to 5.7.1. It is, therefore, affected by multiple vulnerabilities.\n\nNote that Nessus has not tested for these issues but has instead relied only on the application's self-reported version number.",
-                    "Family": "Misc.",
-                    "ID": "117672",
-                    "Name": "Tenable SecurityCenter \u003c 5.7.1 Multiple Vulnerabilities (TNS-2018-12)",
-                    "Severity": "High",
-                    "Total": "2"
-                },
-                {
-                    "Description": "An update for kernel is now available for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having a security impact of Important. A Common Vulnerability Scoring System (CVSS) base score, which gives a detailed severity rating, is available for each vulnerability from the CVE link(s) in the References section.\n\nThe kernel packages contain the Linux kernel, the core of any Linux operating system.\n\nSecurity Fix(es) :\n\n* kernel: Integer overflow in Linux's create_elf_tables function (CVE-2018-14634)\n\nFor more details about the security issue(s), including the impact, a CVSS score, and other related information, refer to the CVE page(s) listed in the References section.\n\nRed Hat would like to thank Qualys Research Labs for reporting this issue.\n\nBug Fix(es) :\n\nThese updated kernel packages include also numerous bug fixes. Space precludes documenting all of the bug fixes in this advisory. See the descriptions in the related Knowledge Article :\n\nhttps://access.redhat.com/articles/3588731",
-                    "Family": "CentOS Local Security Checks",
-                    "ID": "117829",
-                    "Name": "CentOS 7 : kernel (CESA-2018:2748)",
-                    "Severity": "High",
-                    "Total": "1"
-                },
-                {
-                    "Description": "Updated X.org server and driver packages are now available for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having a security impact of Low. A Common Vulnerability Scoring System (CVSS) base score, which gives a detailed severity rating, is available for each vulnerability from the CVE link (s) in the References section.\n\nX.Org is an open source implementation of the X Window System. It provides the basic low-level functionality that full-fledged graphical user interfaces are designed upon.\n\nSecurity Fix(es) :\n\n* libxcursor: 1-byte heap-based overflow in _XcursorThemeInherits function in library.c (CVE-2015-9262)\n\nFor more details about the security issue(s), including the impact, a CVSS score, and other related information, refer to the CVE page(s) listed in the References section.\n\nAdditional Changes :\n\nFor detailed information on changes in this release, see the Red Hat Enterprise Linux 7.6 Release Notes linked from the References section.",
-                    "Family": "CentOS Local Security Checks",
-                    "ID": "118986",
-                    "Name": "CentOS 7 : freeglut / libX11 / libXcursor / libXfont / libXfont2 / libXres / libdrm / libepoxy / etc (CESA-2018:3059)",
-                    "Severity": "High",
-                    "Total": "1"
-                },
-                {
-                    "Description": "An update for kernel is now available for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having a security impact of Important. A Common Vulnerability Scoring System (CVSS) base score, which gives a detailed severity rating, is available for each vulnerability from the CVE link(s) in the References section.\n\nThe kernel packages contain the Linux kernel, the core of any Linux operating system.\n\nSecurity Fix(es) :\n\n* A flaw named FragmentSmack was found in the way the Linux kernel handled reassembly of fragmented IPv4 and IPv6 packets. A remote attacker could use this flaw to trigger time and calculation expensive fragment reassembly algorithm by sending specially crafted packets which could lead to a CPU saturation and hence a denial of service on the system. (CVE-2018-5391)\n\n* kernel: out-of-bounds access in the show_timer function in kernel/time/ posix-timers.c (CVE-2017-18344)\n\n* kernel: Integer overflow in udl_fb_mmap() can allow attackers to execute code in kernel space (CVE-2018-8781)\n\n* kernel: MIDI driver race condition leads to a double-free (CVE-2018-10902)\n\n* kernel: Missing check in inode_init_owner() does not clear SGID bit on non-directories for non-members (CVE-2018-13405)\n\n* kernel: AIO write triggers integer overflow in some protocols (CVE-2015-8830)\n\n* kernel: Use-after-free in snd_pcm_info function in ALSA subsystem potentially leads to privilege escalation (CVE-2017-0861)\n\n* kernel: Handling of might_cancel queueing is not properly pretected against race (CVE-2017-10661)\n\n* kernel: Salsa20 encryption algorithm does not correctly handle zero-length inputs allowing local attackers to cause denial of service (CVE-2017-17805)\n\n* kernel: Inifinite loop vulnerability in madvise_willneed() function allows local denial of service (CVE-2017-18208)\n\n* kernel: fuse-backed file mmap-ed onto process cmdline arguments causes denial of service (CVE-2018-1120)\n\n* kernel: a NULL pointer dereference in dccp_write_xmit() leads to a system crash (CVE-2018-1130)\n\n* kernel: drivers/block/loop.c mishandles lo_release serialization allowing denial of service (CVE-2018-5344)\n\n* kernel: Missing length check of payload in _sctp_make_chunk() function allows denial of service (CVE-2018-5803)\n\n* kernel: buffer overflow in drivers/net/wireless/ath/wil6210/ wmi.c:wmi_set_ie() may lead to memory corruption (CVE-2018-5848)\n\n* kernel: out-of-bound write in ext4_init_block_bitmap function with a crafted ext4 image (CVE-2018-10878)\n\n* kernel: Improper validation in bnx2x network card driver can allow for denial of service attacks via crafted packet (CVE-2018-1000026)\n\n* kernel: Information leak when handling NM entries containing NUL (CVE-2016-4913)\n\n* kernel: Mishandling mutex within libsas allowing local Denial of Service (CVE-2017-18232)\n\n* kernel: NULL pointer dereference in ext4_process_freed_data() when mounting crafted ext4 image (CVE-2018-1092)\n\n* kernel: NULL pointer dereference in ext4_xattr_inode_hash() causes crash with crafted ext4 image (CVE-2018-1094)\n\n* kernel: vhost: Information disclosure in vhost/vhost.c:vhost_new_msg() (CVE-2018-1118)\n\n* kernel: Denial of service in resv_map_release function in mm/hugetlb.c (CVE-2018-7740)\n\n* kernel: Memory leak in the sas_smp_get_phy_events function in drivers/scsi/ libsas/sas_expander.c (CVE-2018-7757)\n\n* kernel: Invalid pointer dereference in xfs_ilock_attr_map_shared() when mounting crafted xfs image allowing denial of service (CVE-2018-10322)\n\n* kernel: use-after-free detected in ext4_xattr_set_entry with a crafted file (CVE-2018-10879)\n\n* kernel: out-of-bound access in ext4_get_group_info() when mounting and operating a crafted ext4 image (CVE-2018-10881)\n\n* kernel: stack-out-of-bounds write in jbd2_journal_dirty_metadata function (CVE-2018-10883)\n\n* kernel: incorrect memory bounds check in drivers/cdrom/cdrom.c (CVE-2018-10940)\n\nRed Hat would like to thank Juha-Matti Tilli (Aalto University - Department of Communications and Networking and Nokia Bell Labs) for reporting CVE-2018-5391; Trend Micro Zero Day Initiative for reporting CVE-2018-10902; Qualys Research Labs for reporting CVE-2018-1120;\nEvgenii Shatokhin (Virtuozzo Team) for reporting CVE-2018-1130; and Wen Xu for reporting CVE-2018-1092 and CVE-2018-1094.",
-                    "Family": "CentOS Local Security Checks",
-                    "ID": "118990",
-                    "Name": "CentOS 7 : kernel (CESA-2018:3083)",
-                    "Severity": "High",
-                    "Total": "1"
-                },
-                {
-                    "Description": "An update for glibc is now available for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having a security impact of Moderate. A Common Vulnerability Scoring System (CVSS) base score, which gives a detailed severity rating, is available for each vulnerability from the CVE link(s) in the References section.\n\nThe glibc packages provide the standard C libraries (libc), POSIX thread libraries (libpthread), standard math libraries (libm), and the name service cache daemon (nscd) used by multiple programs on the system. Without these libraries, the Linux system cannot function correctly.\n\nSecurity Fix(es) :\n\n* glibc: Incorrect handling of RPATH in elf/dl-load.c can be used to execute code loaded from arbitrary libraries (CVE-2017-16997)\n\n* glibc: Integer overflow in posix_memalign in memalign functions (CVE-2018-6485)\n\n* glibc: Integer overflow in stdlib/canonicalize.c on 32-bit architectures leading to stack-based buffer overflow (CVE-2018-11236)\n\n* glibc: Buffer overflow in __mempcpy_avx512_no_vzeroupper (CVE-2018-11237)\n\nFor more details about the security issue(s), including the impact, a CVSS score, and other related information, refer to the CVE page(s) listed in the References section.\n\nAdditional Changes :\n\nFor detailed information on changes in this release, see the Red Hat Enterprise Linux 7.6 Release Notes linked from the References section.",
-                    "Family": "CentOS Local Security Checks",
-                    "ID": "118992",
-                    "Name": "CentOS 7 : glibc (CESA-2018:3092)",
-                    "Severity": "High",
-                    "Total": "1"
-                },
-                {
-                    "Description": "An update is now available for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having a security impact of Moderate. A Common Vulnerability Scoring System (CVSS) base score, which gives a detailed severity rating, is available for each vulnerability from the CVE link(s) in the References section.\n\nGNOME is the default desktop environment of Red Hat Enterprise Linux.\n\nSecurity Fix(es) :\n\n* libsoup: Crash in soup_cookie_jar.c:get_cookies() on empty hostnames (CVE-2018-12910)\n\n* poppler: Infinite recursion in fofi/FoFiType1C.cc:FoFiType1C::cvtGlyph() function allows denial of service (CVE-2017-18267)\n\n* libgxps: heap based buffer over read in ft_font_face_hash function of gxps-fonts.c (CVE-2018-10733)\n\n* libgxps: Stack-based buffer overflow in calling glib in gxps_images_guess_content_type of gcontenttype.c (CVE-2018-10767)\n\n* poppler: NULL pointer dereference in Annot.h:AnnotPath::getCoordsLength() allows for denial of service via crafted PDF (CVE-2018-10768)\n\n* poppler: out of bounds read in pdfunite (CVE-2018-13988)\n\nFor more details about the security issue(s), including the impact, a CVSS score, and other related information, refer to the CVE page(s) listed in the References section.\n\nRed Hat would like to thank chenyuan (NESA Lab) for reporting CVE-2018-10733 and CVE-2018-10767 and Hosein Askari for reporting CVE-2018-13988.\n\nAdditional Changes :\n\nFor detailed information on changes in this release, see the Red Hat Enterprise Linux 7.6 Release Notes linked from the References section.",
-                    "Family": "CentOS Local Security Checks",
-                    "ID": "118995",
-                    "Name": "CentOS 7 : PackageKit / accountsservice / adwaita-icon-theme / appstream-data / at-spi2-atk / etc (CESA-2018:3140)",
-                    "Severity": "High",
-                    "Total": "1"
-                },
-                {
-                    "Description": "An update for curl and nss-pem is now available for Red Hat Enterprise Linux 7.\n\nRed Hat Product Security has rated this update as having a security impact of Moderate. A Common Vulnerability Scoring System (CVSS) base score, which gives a detailed severity rating, is available for each vulnerability from the CVE link(s) in the References section.\n\nThe curl packages provide the libcurl library and the curl utility for downloading files from servers using various protocols, including HTTP, FTP, and LDAP.\n\nThe nss-pem package provides the PEM file reader for Network Security Services (NSS) implemented as a PKCS#11 module.\n\nSecurity Fix(es) :\n\n* curl: HTTP authentication leak in redirects (CVE-2018-1000007)\n\n* curl: FTP path trickery leads to NIL byte out of bounds write (CVE-2018-1000120)\n\n* curl: RTSP RTP buffer over-read (CVE-2018-1000122)\n\n* curl: Out-of-bounds heap read when missing RTSP headers allows information leak of denial of service (CVE-2018-1000301)\n\n* curl: LDAP NULL pointer dereference (CVE-2018-1000121)\n\nFor more details about the security issue(s), including the impact, a CVSS score, and other related information, refer to the CVE page(s) listed in the References section.\n\nRed Hat would like to thank the Curl project for reporting these issues. Upstream acknowledges Craig de Stigter as the original reporter of CVE-2018-1000007; Duy Phan Thanh as the original reporter of CVE-2018-1000120; Max Dymond as the original reporter of CVE-2018-1000122; the OSS-fuzz project as the original reporter of CVE-2018-1000301; and Dario Weisser as the original reporter of CVE-2018-1000121.\n\nAdditional Changes :\n\nFor detailed information on changes in this release, see the Red Hat Enterprise Linux 7.6 Release Notes linked from the References section.",
-                    "Family": "CentOS Local Security Checks",
-                    "ID": "118996",
-                    "Name": "CentOS 7 : curl / nss-pem (CESA-2018:3157)",
-                    "Severity": "High",
-                    "Total": "1"
-                }
-            ]
-        }
-    }
-}
-
-
Human Readable Output
-

image
image

-

6. Get a list of credentials

-
-

Returns a list of Tenable.sc credentials.

-
Base Command
-

tenable-sc-list-credentials

-
Input
- - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
manageableWhether to return only manageable scan credentials. By default, returns both usable and manageable.Optional
-

 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.Credential.NamestringCredential name.
TenableSC.Credential.IDnumberCredential ID.
TenableSC.Credential.DescriptionstringCredential description.
TenableSC.Credential.TypestringCredential type.
TenableSC.Credential.TagstringCredential tag.
TenableSC.Credential.GroupstringCredential owner group name.
TenableSC.Credential.OwnerstringCredential owner user name.
TenableSC.Credential.LastModifieddateCredential last modified time.
-

 

-
Command Example
-
!tenable-sc-list-credentials
-
Context Example
-
{
-    "TenableSC": {
-        "Credential": [
-            {
-                "ID": "1",
-                "LastModified": "2017-10-30T21:17:34Z",
-                "Name": "asdfasdf",
-                "Type": "windows"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000001",
-                "LastModified": "2016-06-23T14:59:38Z",
-                "Name": "cloris_windows_p1",
-                "Type": "windows"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000002",
-                "LastModified": "2017-04-06T10:32:54Z",
-                "Name": "cred admin api30",
-                "Type": "windows"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000003",
-                "LastModified": "2017-04-19T14:04:21Z",
-                "Name": "151",
-                "Type": "windows"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000004",
-                "LastModified": "2017-05-15T22:12:38Z",
-                "Name": "TestSSH creds",
-                "Type": "ssh"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000005",
-                "LastModified": "2017-11-17T15:42:11Z",
-                "Name": "Thycotic Test",
-                "Type": "windows"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000006",
-                "LastModified": "2018-05-10T20:11:27Z",
-                "Name": "testAPI",
-                "Tag": "testAPI",
-                "Type": "windows"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000007",
-                "LastModified": "2018-05-30T16:22:02Z",
-                "Name": "Test",
-                "Type": "database"
-            },
-            {
-                "Description": "asgasdg",
-                "Group": "Full Access",
-                "ID": "1000008",
-                "LastModified": "2018-05-30T16:22:42Z",
-                "Name": "awefawef",
-                "Tag": "testAPI",
-                "Type": "windows"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000009",
-                "LastModified": "2018-05-30T16:23:00Z",
-                "Name": "oracle",
-                "Type": "database"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000010",
-                "LastModified": "2018-05-30T16:23:18Z",
-                "Name": "KerbTest",
-                "Type": "windows"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000011",
-                "LastModified": "2018-05-30T16:23:28Z",
-                "Name": "snmpTest",
-                "Type": "snmp"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000012",
-                "LastModified": "2018-05-30T16:23:43Z",
-                "Name": "lmhash",
-                "Type": "windows"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000013",
-                "LastModified": "2018-05-30T16:24:00Z",
-                "Name": "ntlmhash",
-                "Type": "windows"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000014",
-                "LastModified": "2018-05-30T16:24:24Z",
-                "Name": "thycoti_secret",
-                "Type": "windows"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000015",
-                "LastModified": "2018-05-30T16:24:56Z",
-                "Name": "sshcert",
-                "Type": "ssh"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000016",
-                "LastModified": "2018-05-30T16:25:10Z",
-                "Name": "sshpassword",
-                "Type": "ssh"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000017",
-                "LastModified": "2018-05-30T17:34:43Z",
-                "Name": "SSHPublic Key",
-                "Type": "ssh"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000018",
-                "LastModified": "2018-11-06T19:34:13Z",
-                "Name": "SymbolPassword Test",
-                "Type": "windows"
-            }
-        ]
-    }
-}
-
-
Human Readable Output
-

image

-

7. Get a list of scan policies

-
-

Returns a list of Tenable.sc scan policies.

-
Base Command
-

tenable-sc-list-policies

-
Input
- - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
manageableWhether to return only manageable scan policies. By default, returns both usable and manageable.Optional
-

 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.ScanPolicy.NamestringScan policy name.
TenableSC.ScanPolicy.IDnumberScan policy ID.
TenableSC.ScanPolicy.DescriptionstringScan policy description.
TenableSC.ScanPolicy.TagstringScan policy tag.
TenableSC.ScanPolicy.GroupstringScan policy owner group name.
TenableSC.ScanPolicy.OwnerstringScan policy owner user name.
TenableSC.ScanPolicy.LastModifieddateScan policy last modified time.
TenableSC.ScanPolicy.TypestringScan policy type.
-

 

-
Command Example
-
!tenable-sc-list-policies
-
Context Example
-
{
-    "TenableSC": {
-        "ScanPolicy": [
-            {
-                "Group": "Full Access",
-                "ID": "1000001",
-                "LastModified": "2016-05-04T11:35:27Z",
-                "Name": "MV Scan Policy",
-                "Owner": "API7",
-                "Type": "Advanced Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000002",
-                "LastModified": "2016-05-04T11:35:58Z",
-                "Name": "Web Application Tests",
-                "Owner": "API7",
-                "Type": "Web Application Tests"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000003",
-                "LastModified": "2016-05-04T11:36:25Z",
-                "Name": "Basic Network Scan",
-                "Owner": "API7",
-                "Type": "Basic Network Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000004",
-                "LastModified": "2016-06-23T14:41:08Z",
-                "Name": "Windows Malware Scan",
-                "Owner": "API17",
-                "Type": "Malware Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000005",
-                "LastModified": "2017-03-25T03:28:13Z",
-                "Name": "Compliance Test SC Host",
-                "Owner": "tenable",
-                "Type": "Policy Compliance Auditing"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000006",
-                "LastModified": "2017-04-04T13:05:25Z",
-                "Name": "Maiware Scan",
-                "Owner": "API30",
-                "Type": "Malware Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000008",
-                "LastModified": "2017-04-24T18:12:39Z",
-                "Name": "Basic Discovery Scan",
-                "Owner": "API33",
-                "Type": "Host Discovery"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000009",
-                "LastModified": "2017-05-17T00:43:07Z",
-                "Name": "Test Citrix",
-                "Owner": "API34",
-                "Type": "Advanced Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000010",
-                "LastModified": "2017-05-17T00:44:20Z",
-                "Name": "test juniper",
-                "Owner": "API34",
-                "Type": "Advanced Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000011",
-                "LastModified": "2017-05-17T00:45:02Z",
-                "Name": "test vmware",
-                "Owner": "API34",
-                "Type": "Advanced Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000012",
-                "LastModified": "2017-05-17T23:49:02Z",
-                "Name": "Test PaloAlto Template",
-                "Owner": "API34",
-                "Type": "Advanced Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000014",
-                "LastModified": "2017-09-20T16:41:40Z",
-                "Name": "Full Scan",
-                "Owner": "tenable",
-                "Type": "Basic Network Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000015",
-                "LastModified": "2017-10-17T08:05:13Z",
-                "Name": "cisco_compliance",
-                "Owner": "API32",
-                "Type": "Advanced Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000125",
-                "LastModified": "2018-02-15T15:52:22Z",
-                "Name": "test_9845771654157357",
-                "Owner": "API61",
-                "Type": "Basic Network Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000165",
-                "LastModified": "2018-04-10T19:23:00Z",
-                "Name": "Test CIS",
-                "Owner": "example.gmail.com",
-                "Type": "Policy Compliance Auditing"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000568",
-                "LastModified": "2018-08-27T06:37:46Z",
-                "Name": "Basic_Disc",
-                "Owner": "API25",
-                "Type": "Basic Network Scan"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "1000619",
-                "LastModified": "2018-11-06T19:35:24Z",
-                "Name": "Symbol Password tests",
-                "Owner": "hammackj",
-                "Type": "Advanced Scan"
-            }
-        ]
-    }
-}
-
-
Human Readable Output
-

image

-

8. Get a list of report definitions

-
-

Returns a list of Tenable.sc report definitions.

-
Base Command
-

tenable-sc-list-report-definitions

-
Input
- - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
manageableWhether to return only manageable reports. By default, returns both usable and manageable.Optional
-

 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.ReportDefinition.NamestringReport definition name.
TenableSC.ReportDefinition.IDnumberReport definition ID.
TenableSC.ReportDefinition.DescriptionstringReport definition description.
TenableSC.ReportDefinition.TypestringReport definition type.
TenableSC.ReportDefinition.GroupstringReport definition owner group name.
TenableSC.ReportDefinition.OwnerstringReport definition owner user name.
-

 

-
Command Example
-
!tenable-sc-list-report-definitions manageable=true
-
Context Example
-
{
-    "TenableSC": {
-        "ReportDefinition": [
-            {
-                "Group": "Full Access",
-                "ID": "439",
-                "Name": "Monthly Executive Report",
-                "Owner": "API55",
-                "Type": "pdf"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "440",
-                "Name": "Remediation Instructions by Host Report",
-                "Owner": "API55",
-                "Type": "pdf"
-            },
-            {
-                "Group": "Full Access",
-                "ID": "438",
-                "Name": "Critical and Exploitable Vulnerabilities Report",
-                "Owner": "API55",
-                "Type": "pdf"
-            }
-        ]
-    }
-}
-
-
Human Readable Output
-

image

-

9. Get a list of scan repositories

-
-

Returns a list of Tenable.sc scan repositories.

-
Base Command
-

tenable-sc-list-repositories

-
Input
-

There is no input for this command. 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.ScanRepository.NamestringScan repository name.
TenableSC.ScanRepository.IDnumberScan repository ID.
TenableSC.ScanRepository.DescriptionstringScan repository.
-

 

-
Command Example
-
!tenable-sc-list-repositories
-
Context Example
-
{
-    "TenableSC": {
-        "ScanRepository": [
-            {
-                "ID": "1",
-                "Name": "repo"
-            },
-            {
-                "ID": "2",
-                "Name": "Offline Repo"
-            },
-            {
-                "ID": "3",
-                "Name": "agent_repo"
-            }
-        ]
-    }
-}
-
-
Human Readable Output
-

image

-

10. Get a list of scan zones

-
-

Returns a list of Tenable.sc scan zones.

-
Base Command
-

tenable-sc-list-zones

-
Input
-

There is no input for this command. 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.ScanZone.NamestringScan zone name.
TenableSC.ScanZone.IDnumberScan zone ID.
TenableSC.ScanZone.DescriptionstringScan zone description.
TenableSC.ScanZone.IPListunknownScan zone IP list.
TenableSC.ScanZone.ActiveScannersnumberScan zone active scanners.
-

 

-
Command Example
-
!tenable-sc-list-zones
-
Context Example
-
{
-    "TenableSC": {
-        "ScanZone": {
-            "ID": 0,
-            "Name": "All Zones"
-        }
-    }
-}
-
-
Human Readable Output
-

image

-

11. Create a scan

-
-

Creates a scan on Tenable.sc.

-
Base Command
-

tenable-sc-create-scan

-
Input
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
nameScan name.Required
policy_idPolicy ID (can be retrieved from the tenable-sc-list-policies command).Required
descriptionScan description.Optional
repository_idScan Repository ID (can be retrieved from the tenable-sc-list-repositories command).Required
zone_idScan zone ID (default is all zones) (can be retrieved from the tenable-sc-list-zones command).Optional
scheduleSchedule for the scan.Optional
asset_idsEither all assets or a comma-separated list of asset IDs to scan (can be retrieved from the tenable-sc-list-assets command).Optional
scan_virtual_hostsWhether to include virtual hosts, default is false.Optional
ip_listComma-separated list of IPs to scan, e.g., 10.0.0.1,10.0.0.2.Optional
report_idsComma separated list of report definition IDs to create post-scan, can be retrieved from list-report-definitions command.Optional
credentialsComma-separated credentials IDs to use (can be retrieved from the tenable-sc-list-credentials command).Optional
timeout_actionScan timeout action, default is import.Optional
max_scan_timeMaximum scan run time in hours, default is 1.Optional
dhcp_trackingTrack hosts which have been issued new IP address, (e.g. DHCP).Optional
rollover_typeScan rollover type.Optional
dependent_idDependent scan ID in case of a dependent schedule, can be retrieved from list-scans command.Optional
-

 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.Scan.IDstringScan ID.
TenableSC.Scan.CreatorIDstringScan's creator ID.
TenableSC.Scan.NamestringScan name.
TenableSC.Scan.TypestringScan type.
TenableSC.Scan.CreatedTimedateScan creation time.
TenableSC.Scan.OwnerNamestringScan owner username.
TenableSC.Scan.ReportsunknownScan report definition IDs.
-

 

-
Command Example
-
!tenable-sc-create-scan name="test_scan_2018" policy_id="1000618" description="Test scan" repository_id="1" schedule="never" asset_ids=AllManageable scan_virtual_hosts="false" ip_list="10.0.0.1" report_ids="438" credentials="1000007" max_scan_time="2" dhcp_tracking="true"
-
Context Example
-
{
-    "TenableSC": {
-        "Scan": {
-            "CreationTime": "2018-11-26T17:29:02Z",
-            "CreatorID": "53",
-            "ID": "1286",
-            "Name": "test_scan_2018",
-            "Reports": [
-                "438"
-            ],
-            "Type": "policy"
-        }
-    }
-}
-
-
Human Readable Output
-

image

-

12. Delete a scan

-
-

Deletes a scan in Tenable.sc.

-
Base Command
-

tenable-sc-delete-scan

-
Input
- - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
scan_idScan ID (can be retrieved from the tenable-sc-list-scans command).Required
-

 

-
Context Output
-

There is no context output for this command.

-
Command Example
-
!tenable-sc-delete-scan scan_id=1286
-
Human Readable Output
-

image

-

13. Get a list of assets

-
-

Returns a list of Tenable.sc assets.

-
Base Command
-

tenable-sc-list-assets

-
Input
- - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
manageableWhether to return only manageable assets.By default, returns both usable and manageable.Optional
-

 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.Asset.IDstringAsset ID.
TenableSC.Asset.NamestringAsset name.
TenableSC.Asset.HostCountnumberAsset host IPs count.
TenableSC.Asset.TypestringAsset type.
TenableSC.Asset.TagstringAsset tag.
TenableSC.Asset.OwnerstringAsset owner username.
TenableSC.Asset.GroupstringAsset group.
TenableSC.Asset.LastModifieddateAsset last modified time.
-

 

-
Command Example
-
!tenable-sc-list-assets manageable=true
-
Context Example
-
{
-    "TenableSC": {
-        "Asset": [
-            {
-                "HostCount": 0,
-                "ID": "354",
-                "LastModified": "2018-01-08T13:50:05Z",
-                "Name": "Bad Credentials",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 0,
-                "ID": "355",
-                "LastModified": "2018-01-08T13:50:08Z",
-                "Name": "Bad Windows Account",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 5,
-                "ID": "356",
-                "LastModified": "2018-01-08T13:50:09Z",
-                "Name": "Windows Hosts",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 0,
-                "ID": "357",
-                "LastModified": "2018-01-08T13:50:11Z",
-                "Name": "Windows 7",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 0,
-                "ID": "358",
-                "LastModified": "2018-01-08T13:50:13Z",
-                "Name": "Windows RDP or Terminal Services",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 2,
-                "ID": "359",
-                "LastModified": "2018-01-08T13:50:15Z",
-                "Name": "WMI Login Authenticated",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 0,
-                "ID": "360",
-                "LastModified": "2018-01-08T13:50:16Z",
-                "Name": "Microsoft Office 2010",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 0,
-                "ID": "361",
-                "LastModified": "2018-01-08T13:50:18Z",
-                "Name": "Microsoft Office 2007",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 0,
-                "ID": "362",
-                "LastModified": "2018-01-08T13:50:19Z",
-                "Name": "Microsoft VPN Technology",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 0,
-                "ID": "363",
-                "LastModified": "2018-01-08T13:50:21Z",
-                "Name": "Microsoft Windows Server 2000",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 4,
-                "ID": "364",
-                "LastModified": "2018-01-08T13:50:23Z",
-                "Name": "Microsoft Windows Server",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 0,
-                "ID": "365",
-                "LastModified": "2018-01-08T13:50:24Z",
-                "Name": "Microsoft Windows Server 2003",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 3,
-                "ID": "366",
-                "LastModified": "2018-01-08T13:50:26Z",
-                "Name": "Microsoft Windows Server 2008",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 1,
-                "ID": "367",
-                "LastModified": "2018-01-08T13:50:28Z",
-                "Name": "Microsoft Windows Server 2012",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 2,
-                "ID": "368",
-                "LastModified": "2018-01-08T13:50:29Z",
-                "Name": "Microsoft Windows Server Datacenter",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 0,
-                "ID": "369",
-                "LastModified": "2018-01-08T13:50:31Z",
-                "Name": "Microsoft Windows Server Enterprise",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 0,
-                "ID": "370",
-                "LastModified": "2018-01-08T13:50:33Z",
-                "Name": "Microsoft Windows Server Standard",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 0,
-                "ID": "371",
-                "LastModified": "2018-01-08T13:50:36Z",
-                "Name": "Microsoft Windows Workstation Enterprise",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 0,
-                "ID": "372",
-                "LastModified": "2018-01-08T13:50:37Z",
-                "Name": "Microsoft Windows Workstation Home",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 0,
-                "ID": "373",
-                "LastModified": "2018-01-08T13:50:39Z",
-                "Name": "Microsoft Windows 8",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 0,
-                "ID": "374",
-                "LastModified": "2018-01-08T13:50:40Z",
-                "Name": "Microsoft Windows Workstation Ultimate",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 0,
-                "ID": "375",
-                "LastModified": "2018-01-08T13:50:42Z",
-                "Name": "Unsupported Windows Operating Systems",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 0,
-                "ID": "376",
-                "LastModified": "2018-01-08T13:50:43Z",
-                "Name": "Microsoft Windows Workstation Professional",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 0,
-                "ID": "377",
-                "LastModified": "2018-01-08T13:50:45Z",
-                "Name": "Microsoft Windows XP",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": 0,
-                "ID": "392",
-                "LastModified": "2018-06-11T16:45:26Z",
-                "Name": "Malware or Malicious Processes",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": "1",
-                "ID": "537",
-                "LastModified": "2018-11-07T13:34:11Z",
-                "Name": "Maya test Asset",
-                "Owner": "API55",
-                "Type": "static"
-            },
-            {
-                "HostCount": 0,
-                "ID": "538",
-                "LastModified": "2018-11-07T13:35:12Z",
-                "Name": "Malware or Malicious Processes(1)",
-                "Owner": "API55",
-                "Type": "dynamic"
-            },
-            {
-                "HostCount": "1",
-                "ID": "543",
-                "LastModified": "2018-11-20T18:29:53Z",
-                "Name": "test_asset",
-                "Owner": "API55",
-                "Type": "static"
-            },
-            {
-                "HostCount": "2",
-                "ID": "544",
-                "LastModified": "2018-11-20T18:31:51Z",
-                "Name": "test_asset2",
-                "Owner": "API55",
-                "Type": "static"
-            },
-            {
-                "HostCount": "2",
-                "ID": "545",
-                "LastModified": "2018-11-20T18:32:21Z",
-                "Name": "test_asset3",
-                "Owner": "API55",
-                "Type": "static"
-            },
-            {
-                "HostCount": "2",
-                "ID": "546",
-                "LastModified": "2018-11-20T18:35:28Z",
-                "Name": "test_asset4",
-                "Owner": "API55",
-                "Type": "static"
-            },
-            {
-                "HostCount": "2",
-                "ID": "547",
-                "LastModified": "2018-11-20T18:36:07Z",
-                "Name": "test_asset5",
-                "Owner": "API55",
-                "Type": "static"
-            },
-            {
-                "HostCount": "2",
-                "ID": "548",
-                "LastModified": "2018-11-21T15:40:52Z",
-                "Name": "blah",
-                "Owner": "API55",
-                "Type": "static"
-            },
-            {
-                "HostCount": "1",
-                "ID": "549",
-                "LastModified": "2018-11-21T16:05:10Z",
-                "Name": "test_asset9",
-                "Owner": "API55",
-                "Tag": "hmm,blob",
-                "Type": "static"
-            },
-            {
-                "HostCount": "2",
-                "ID": "550",
-                "LastModified": "2018-11-22T15:12:29Z",
-                "Name": "yyyy",
-                "Owner": "API55",
-                "Type": "static"
-            },
-            {
-                "HostCount": "1",
-                "ID": "551",
-                "LastModified": "2018-11-25T16:06:39Z",
-                "Name": "test_asset_Sun Nov 25 2018 18:06:35 GMT+0200 (IST)",
-                "Owner": "API55",
-                "Type": "static"
-            },
-            {
-                "HostCount": "1",
-                "ID": "552",
-                "LastModified": "2018-11-25T16:08:54Z",
-                "Name": "test_asset_Sun Nov 25 2018 18:08:50 GMT+0200 (IST)",
-                "Owner": "API55",
-                "Type": "static"
-            },
-            {
-                "HostCount": "1",
-                "ID": "556",
-                "LastModified": "2018-11-25T16:18:56Z",
-                "Name": "test_asset_Sun Nov 25 2018 18:18:52 GMT+0200 (IST)",
-                "Owner": "API55",
-                "Type": "static"
-            },
-            {
-                "HostCount": "1",
-                "ID": "557",
-                "LastModified": "2018-11-25T16:34:52Z",
-                "Name": "test_asset_Sun Nov 25 2018 18:34:47 GMT+0200 (IST)",
-                "Owner": "API55",
-                "Type": "static"
-            },
-            {
-                "HostCount": "1",
-                "ID": "558",
-                "LastModified": "2018-11-26T08:20:09Z",
-                "Name": "test_asset_Mon Nov 26 2018 10:20:05 GMT+0200 (IST)",
-                "Owner": "API55",
-                "Type": "static"
-            },
-            {
-                "HostCount": "1",
-                "ID": "690",
-                "LastModified": "2018-11-26T16:10:08Z",
-                "Name": "test_asset_Mon Nov 26 2018 18:10:02 GMT+0200 (IST)",
-                "Owner": "API55",
-                "Type": "static"
-            }
-        ]
-    }
-}
-
-
Human Readable Output
-

image

-

14. Create an asset

-
-

Creates an asset in Tenable.sc with the specified IP addresses.

-
Base Command
-

tenable-sc-create-asset

-
Input
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
nameAsset name.Required
descriptionAsset description.Optional
owner_idAsset owner ID, default is the Session User ID (can be retrieved from the tenable-sc-list-users command).Optional
tagAsset tag.Optional
ip_listComma-separated list of IPs to include in the asset, e.g., 10.0.0.2,10.0.0.4Required
-

 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.Asset.NamestringAsset name.
TenableSC.Asset.IDstringAsset ID.
TenableSC.Asset.OwnerNamestringAsset owner name.
TenableSC.Asset.TagsstringAsset tags.
-

 

-
Command Example
-
!tenable-sc-create-asset name="test_asset_2018" description="desc" owner_id="53" ip_list="10.0.0.1,10.0.0.2"
-
Context Example
-
{
-    "TenableSC": {
-        "Asset": {
-            "ID": "691",
-            "Name": "test_asset_2018",
-            "OwnerName": "API55"
-        }
-    }
-}
-
-
Human Readable Output
-

image

-

15. Get asset information

-
-

Get details for a given asset in Tenable.sc

-
Base Command
-

tenable-sc-get-asset

-
Input
- - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
asset_idAsset ID (can be retrieved from the tenable-sc-list-assets command).Required
-

 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.Asset.IDnumberAsset ID.
TenableSC.Asset.NamestringAsset name.
TenableSC.Asset.DescriptionstringAsset description.
TenableSC.Asset.TagstringAsset tag.
TenableSC.Asset.ModifieddateAsset last modified time.
TenableSC.Asset.OwnerstringAsset owner user name.
TenableSC.Asset.GroupstringAsset owner group.
TenableSC.Asset.IPsunknownAsset viewable IPs.
-

 

-
Command Example
-
!tenable-sc-get-asset asset_id=691
-
Context Example
-
{
-    "TenableSC": {
-        "Asset": {
-            "Created": "2018-11-26T18:17:39Z",
-            "Description": "desc",
-            "Group": "Full Access",
-            "ID": "691",
-            "IPs": [
-                "10.0.0.1",
-                "10.0.0.2"
-            ],
-            "Modified": "2018-11-26T18:17:39Z",
-            "Name": "test_asset_2018",
-            "Owner": "API55"
-        }
-    }
-}
-
-
Human Readable Output
-

image

-

16. Delete an asset

-
-

Deletes the asset with the specified asset ID from Tenable.sc.

-
Base Command
-

tenable-sc-delete-asset

-
Input
- - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
asset_idAsset ID.Required
-

 

-
Context Output
-

There is no context output for this command.

-
Command Example
-
!tenable-sc-delete-asset asset_id=691
-
Human Readable Output
-

image

-

17. Get a list of alerts

-
-

Returns a list alerts from Tenable.sc.

-
Base Command
-

tenable-sc-list-alerts

-
Input
- - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
manageableWhether to return only manageable alerts. By default, returns both usable and manageable.Optional
-

 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.Alert.IDstringAlert ID.
TenableSC.Alert.NamestringAlert name.
TenableSC.Alert.DescriptionstringAlert description.
TenableSC.Alert.StatestringAlert state.
TenableSC.Alert.ActionsstringAlert actions.
TenableSC.Alert.LastTriggereddateAlert last triggered time.
TenableSC.Alert.LastEvaluateddateAlert last evaluated time.
TenableSC.Alert.GroupstringAlert owner group name.
TenableSC.Alert.OwnerstringAlert owner user name.
-

 

-
Command Example
-
!tenable-sc-list-alerts
-
Context Example
-
{
-    "TenableSC": {
-        "Alert": [
-            {
-                "Actions": [
-                    "ticket"
-                ],
-                "Group": "Full Access",
-                "ID": "1",
-                "LastEvaluated": "2018-11-25T19:44:00Z",
-                "LastTriggered": "2017-01-31T19:44:01Z",
-                "Name": "bwu_alert1",
-                "Owner": "API17",
-                "State": "Triggered"
-            },
-            {
-                "Actions": [
-                    "notification",
-                    "ticket"
-                ],
-                "Group": "Full Access",
-                "ID": "2",
-                "LastEvaluated": "2018-11-26T18:30:14Z",
-                "LastTriggered": "2018-11-26T18:30:15Z",
-                "Name": "Test Alert",
-                "Owner": "API55",
-                "State": "Triggered"
-            },
-            {
-                "Actions": [
-                    "ticket"
-                ],
-                "Group": "Full Access",
-                "ID": "3",
-                "LastEvaluated": "2018-11-26T18:30:04Z",
-                "LastTriggered": "1970-01-01T00:00:00Z",
-                "Name": "Test fetch",
-                "Owner": "API55",
-                "State": "Not Triggered"
-            }
-        ]
-    }
-}
-
-
Human Readable Output
-

image

-

18. Get alert information

-
-

Returns information about a specified alert in Tenabel.sc.

-
Base Command
-
tenable-sc-get-alert
-
Input
- - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
alert_idAlert ID (can be retrieved from the tenable-sc-list-alerts command).Required
-

 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.Alert.IDstringAlert ID.
TenableSC.Alert.NamestringAlert name.
TenableSC.Alert.DescriptionstringAlert description.
TenableSC.Alert.StatestringAlert state.
TenableSC.Alert.Condition.TriggerstringAlert trigger.
TenableSC.Alert.LastTriggereddateAlert last triggered time.
TenableSC.Alert.ActionstringAlert action type.
TenableSC.Alert.Action.ValuesunknownAlert action values.
TenableSC.Alert.Condition.QuerystringAlert query name.
TenableSC.Alert.Condition.Filter.NamestringAlert query filter name.
TenableSC.Alert.Condition.Filter.ValuesunknownAlert query filter values.
-

 

-
Command Example
-
!tenable-sc-get-alert alert_id=3
-
Context Example
-
{
-    "TenableSC": {
-        "Alert": {
-            "Action": [
-                "type": "ticket",
-                "values": "API55"
-            ],
-            "Behavior": "Execute on every trigger ",
-            "Condition": {
-                "Filter": [
-                    {
-                        "Name": "ip",
-                        "Values": "11.0.0.2"
-                    }
-                ],
-                "Query": "IPv4 Fixed Address: 11.0.0.2",
-                "Trigger": "sumport >= 1"
-            },
-            "ID": "3",
-            "LastTriggered": "Never",
-            "Name": "Test fetch",
-            "State": "Not Triggered"
-        }
-    }
-}
-
Human Readable Output
-

image

-

19. Get device information for a user

-
-

Returns device information from the current user in Tenable.sc.

-
Base Command
-

tenable-sc-get-device

-
Input
- - - - - - - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
ipA valid IP address to filter by.Optional
dnsNameDNS name for the IP address.Optional
-

 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.Device.IPstringDevice IP address.
TenableSC.Device.UUIDstringDevice UUID.
TenableSC.Device.RepositoryIDstringDevice repository ID.
TenableSC.Device.MacAddressstringDevice Mac address.
TenableSC.Device.NetbiosNamestringDevice Netbios name.
TenableSC.Device.DNSNamestringDevice DNS name.
TenableSC.Device.OSstringDevice operating system.
TenableSC.Device.OsCPEstringDevice Common Platform Enumeration.
TenableSC.Device.LastScandateDevice's last scan time.
TenableSC.Device.RepositoryNamestringDevice repository name.
TenableSC.Device.TotalScorenumberDevice total threat score.
TenableSC.Device.LowSeveritynumberDevice total threat scores with low severity.
TenableSC.Device.MediumSeveritynumberDevice total threat scores with medium severity.
TenableSC.Device.HighSeveritynumberDevice total threat scores with high severity.
TenableSC.Device.CriticalSeveritynumberDevice total threat scores with critical severity.
-

 

-
Command Example
-
!tenable-sc-get-device
-
Context Example
-
{
-    "TenableSC": {
-        "Device": {
-            "CriticalSeverity": "0",
-            "DNSName": "gateway",
-            "HighSeverity": "0",
-            "IP": "10.0.0.1",
-            "LastScan": "2018-11-26T18:26:03Z",
-            "LowSeverity": "0",
-            "MacAddress": "12:34:56:78:9a:bc",
-            "MediumSeverity": "0",
-            "OS": "Linux Kernel 2.2 Linux Kernel 2.4 Linux Kernel 2.6",
-            "RepositoryID": "1",
-            "RepositoryName": "repo",
-            "TotalScore": "4"
-        }
-    }
-}
-
-
Human Readable Output
-

image

-

20. Get a list of users

-
-

List users in Tenable.sc.

-
Base Command
-

tenable-sc-list-users

-
Input
- - - - - - - - - - - - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
idFilter by user ID.Optional
usernameFilter by user name.Optional
emailFilter by user email address.Optional
-

 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.User.IDstringUser ID.
TenableSC.User.UsernamestringUsername.
TenableSC.User.FirstNamestringUser first name.
TenableSC.User.LastNamestringUser last name.
TenableSC.User.TitlestringUser title.
TenableSC.User.EmailstringUser email address.
TenableSC.User.CreateddateThe creation time of the user.
TenableSC.User.ModifieddateLast modification time of the user.
TenableSC.User.LogindateUser last login.
TenableSC.User.RolestringUser role name.
-

 

-
Command Example
-
!tenable-sc-list-users username=API55
-
Context Example
-
{
-    "TenableSC": {
-        "User": {
-            "Created": "2017-12-13T20:59:54Z",
-            "FirstName": "API55",
-            "ID": "53",
-            "LastLogin": "2018-11-26T18:52:10Z",
-            "Modified": "2017-12-13T20:59:54Z",
-            "Role": "Security Manager",
-            "Username": "API55"
-        }
-    }
-}
-
-
Human Readable Output
-

image

-

21. Get licensing information

-
-

Retrieves licensing information from Tenable.sc.

-
Base Command
-

tenable-sc-get-system-licensing

-
Input
-

There is no input for this command. 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.Status.ActiveIPSnumberNumber of active IP addresses.
TenableSC.Status.LicensedIPSunknownNumber of licensed IP addresses.
TenableSC.Status.LicenseunknownLicense status.
-

 

-
Command Example
-
!tenable-sc-get-system-licensing
-
Context Example
-
{
-    "TenableSC": {
-        "Status": {
-            "ActiveIPS": "150",
-            "License": "Valid",
-            "LicensedIPS": "1024"
-        }
-    }
-}
-
-
Human Readable Output
-

image

-

22. Get system information and diagnostics

-
-

Returns the system information and diagnostics from Tenable.sc.

-
Base Command
-

tenable-sc-get-system-information

-
Input
-

There is no input for this command. 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.System.VersionstringSystem version.
TenableSC.System.BuildIDstringSystem build ID.
TenableSC.System.ReleaseIDstringSystem release ID.
TenableSC.System.LicensestringSystem license status.
TenableSC.System.JavaStatusbooleanServer Java status.
TenableSC.System.RPMStatusbooleanServer RPM status.
TenableSC.System.DiskStatusbooleanServer disk status.
TenableSC.System.DiskThresholdnumberSystem space left on disk.
TenableSC.System.LastCheckdateSystem last check time.
-

 

-
Command Example
-
!tenable-sc-get-system-information
-

23. Get device information

-
-

Retrieves information for the specified device.

-
Base Command
-

tenable-sc-get-device

-
Input
- - - - - - - - - - - - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
ipA valid IP address of a device.Optional
dns_nameDNS name of a device.Optional
repository_idRepository ID to get the device from, can be retrieved from the list-repositories command.Optional
-

 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.Device.IPstringDevice IP address.
TenableSC.Device.UUIDstringDevice UUID.
TenableSC.Device.RepositoryIDstringDevice repository ID.
TenableSC.Device.MacAddressstringDevice Mac address.
TenableSC.Device.NetbiosNamestringDevice Netbios name.
TenableSC.Device.DNSNamestringDevice DNS name.
TenableSC.Device.OSstringDevice operating system.
TenableSC.Device.OsCPEstringDevice Common Platform Enumeration.
TenableSC.Device.LastScandateDevice's last scan time.
TenableSC.Device.RepositoryNamestringDevice repository name.
TenableSC.Device.TotalScorenumberDevice total threat score.
TenableSC.Device.LowSeveritynumberDevice total threat scores with low severity.
TenableSC.Device.MediumSeveritynumberDevice total threat scores with medium severity.
TenableSC.Device.HighSeveritynumberDevice total threat scores with high severity.
TenableSC.Device.CriticalSeveritynumberDevice total threat scores with critical severity.
Endpoint.IPAddressstringEndpoint IP address.
Endpoint.HostnamestringEndpoint DNS name.
Endpoint.MACAddressstringEndpoint Mac address.
Endpoint.OSstringEndpoint operating system.
-

 

-
Command Example
-
!tenable-sc-get-device ip=213.35.2.109
-!tenable-sc-get-device dns_name=213-35-2-109.navisite.net
-
Context Example
-
{
-    "Endpoint": {
-        "Hostname": "213-35-2-109.navisite.net",
-        "IPAddress": "213.35.2.109",
-        "OS": "Microsoft Windows Server 2012 R2"
-    },
-    "TenableSC": {
-        "Device": {
-            "CriticalSeverity": "0",
-            "DNSName": "213-35-2-109.navisite.net",
-            "HighSeverity": "0",
-            "IP": "213.35.2.109",
-            "LastScan": "2018-12-04T06:27:32Z",
-            "LowSeverity": "0",
-            "MediumSeverity": "0",
-            "OS": "Microsoft Windows Server 2012 R2",
-            "OsCPE": "cpe:/o:microsoft:windows_server_2012:r2",
-            "RepositoryID": "1",
-            "RepositoryName": "repo",
-            "TotalScore": "34"
-        }
-    }
-}
-
-
Human Readable Output
-

image

-

24. Get all scan results

-
-

Returns all scan results in Tenable.sc.

-
Base Command
-

tenable-sc-get-all-scan-results

-
Input
- - - - - - - - - - - - - - - - - - - - - - - - - -
Argument NameDescriptionRequired
manageableFilter only manageable alerts. By default, returns both usable and manageable alerts.Optional
pageThe page to return, starting from 0.Optional
limitThe number of objects to return in one response (maximum limit is 200).Optional
-

 

-
Context Output
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PathTypeDescription
TenableSC.ScanResults.IDNumberScan ID.
TenableSC.ScanResults.NamestringScan name.
TenableSC.ScanResults.StatusstringScan status.
TenableSC.ScanResults.DescriptionstringScan description.
TenableSC.ScanResults.PolicystringScan policy.
TenableSC.ScanResults.GroupstringScan group name.
TenableSC.ScanResults.ChecksnumberScan completed number of checks.
TenableSC.ScanResults.StartTimedateScan results start time.
TenableSC.ScanResults.EndTimedateScan results end time.
TenableSC.ScanResults.DurationnumberScan duration in minutes.
TenableSC.ScanResults.ImportTimedateScan import time.
TenableSC.ScanResults.ScannedIPsnumberNumber of scanned IPs.
TenableSC.ScanResults.OwnerstringScan owner name.
TenableSC.ScanResults.RepositoryNamestringScan repository name.
-

 

-
Command Example
-
  !tenable-sc-get-all-scan-results page=10 limit=30
-
Human Readable Output
-

-

Troubleshooting

-
-

For errors within Tenable.sc, the cause is generally specified, e.g., The currently logged in used is not an administratorUnable to retrieve Asset #2412. Asset #2412 does not exist or Invalid login credentials. However there might be connection errors, for example when the server URL provided is incorrect.

+With Tenable.sc (formerly SecurityCenter) you get a real-time, continuous assessment of your security posture so you can find and fix vulnerabilities faster. +This integration was integrated and tested with version xx of Tenable.sc + +## Configure Tenable.sc on Cortex XSOAR + +1. Navigate to **Settings** > **Integrations** > **Servers & Services**. +2. Search for Tenable.sc. +3. Click **Add instance** to create and configure a new integration instance. + + | **Parameter** | **Required** | + | --- | --- | + | Server URL (e.g. https://192.168.0.1) | True | + | Access key | False | + | Secret key | False | + | Username | False | + | Password | False | + | Trust any certificate (not secure) | False | + | Use system proxy settings | False | + | Fetch incidents | False | + | Incident type | False | + | First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days, 3 months, 1 year) | False | + +4. Click **Test** to validate the URLs, token, and connection. + +## Commands + +You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook. +After you successfully execute a command, a DBot message appears in the War Room with the command details. + +### tenable-sc-list-scans + +*** +Requires security manager authentication. Get a list of Tenable.sc existing scans + +#### Base Command + +`tenable-sc-list-scans` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| manageable | Whether to return only manageable scans. Returns both usable and manageable scans by default. Possible values are: true, false. Default is false. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.Scan.Name | string | Scan name. | +| TenableSC.Scan.ID | number | Scan ID. | +| TenableSC.Scan.Description | string | Scan description. | +| TenableSC.Scan.Policy | string | Scan policy name. | +| TenableSC.Scan.Group | string | Scan policy owner group name. | +| TenableSC.Scan.Owner | string | Scan policy owner user name. | + +### tenable-sc-launch-scan + +*** +Requires security manager authentication. Launch an existing scan from Tenable.sc + +#### Base Command + +`tenable-sc-launch-scan` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| scan_id | Scan ID, can be retrieved from list-scans command. | Required | +| diagnostic_target | Valid IP/Hostname of a specific target to scan. Must be provided with diagnosticPassword. | Optional | +| diagnostic_password | Non empty string password. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.ScanResults.Name | string | Scan name. | +| TenableSC.ScanResults.ID | string | Scan Results ID. | +| TenableSC.ScanResults.OwnerID | string | Scan owner ID. | +| TenableSC.ScanResults.JobID | string | Job ID. | +| TenableSC.ScanResults.Status | string | Scan status. | + +### tenable-sc-get-vulnerability + +*** +Requires security manager authentication. Get details about a given vulnerability from a given Tenable.sc scan + +#### Base Command + +`tenable-sc-get-vulnerability` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| vulnerability_id | Vulnerability ID from the scan-report command. | Required | +| scan_results_id | Scan results ID from the scan-report command. | Optional | +| query_id | Can be created via the Tenable.sc UI > Analysis > queries. can be retrieved from tenable-sc-list-query command. | Optional | +| sort_direction | Default is 'ASC'. Requires companion parameter, sort_field. Possible values are: ASC, DESC. Default is ASC. | Optional | +| sort_field | Which field to sort by, For vulnerabilities data, Tenable recommends you sort by severity. Default is severity. | Optional | +| source_type | When the source_type is "individual", a scan_results_id must be provided. cumulative — Analyzes cumulative vulnerabilities. patched — Analyzes mitigated vulnerabilities. Possible values are: individual, cumulative, patched. Default is individual. | Optional | +| limit | The number of objects to return in one response (maximum limit is 200). Default is 50. | Optional | +| page | The page to return, starting from 0. Default is 0. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.ScanResults.ID | number | Scan results ID. | +| TenableSC.ScanResults.Vulnerability.ID | number | Vulnerability plugin ID. | +| TenableSC.ScanResults.Vulnerability.Name | string | Vulnerability name. | +| TenableSC.ScanResults.Vulnerability.Description | string | Vulnerability description. | +| TenableSC.ScanResults.Vulnerability.Type | string | Vulnerability type. | +| TenableSC.ScanResults.Vulnerability.Severity | string | Vulnerability Severity. | +| TenableSC.ScanResults.Vulnerability.Synopsis | string | Vulnerability Synopsis. | +| TenableSC.ScanResults.Vulnerability.Solution | string | Vulnerability Solution. | +| TenableSC.ScanResults.Vulnerability.Published | date | Vulnerability publish date. | +| TenableSC.ScanResults.Vulnerability.CPE | string | Vulnerability CPE. | +| TenableSC.ScanResults.Vulnerability.CVE | Unknown | Vulnerability CVE. | +| TenableSC.ScanResults.Vulnerability.ExploitAvailable | boolean | Vulnerability exploit available. | +| TenableSC.ScanResults.Vulnerability.ExploitEase | string | Vulnerability exploit ease. | +| TenableSC.ScanResults.Vulnerability.RiskFactor | string | Vulnerability risk factor. | +| TenableSC.ScanResults.Vulnerability.CVSSBaseScore | number | Vulnerability CVSS base score. | +| TenableSC.ScanResults.Vulnerability.CVSSTemporalScore | number | Vulnerability CVSS temporal score. | +| TenableSC.ScanResults.Vulnerability.CVSSVector | string | Vulnerability CVSS vector. | +| TenableSC.ScanResults.Vulnerability.PluginDetails | Unknown | Vulnerability plugin details. | +| CVE.ID | Unknown | CVE ID. | +| TenableSC.ScanResults.Vulnerability.Host.IP | string | Vulnerability Host IP. | +| TenableSC.ScanResults.Vulnerability.Host.MAC | string | Vulnerability Host MAC. | +| TenableSC.ScanResults.Vulnerability.Host.Port | number | Vulnerability Host Port. | +| TenableSC.ScanResults.Vulnerability.Host.Protocol | string | Vulnerability Host Protocol. | + +### tenable-sc-get-scan-status + +*** +Requires security manager authentication. Get the status of a specific scan in Tenable.sc. + +#### Base Command + +`tenable-sc-get-scan-status` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| scan_results_id | Scan results ID from the launch-scan command. | Required | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.ScanResults.Status | string | Scan status. | +| TenableSC.ScanResults.Name | string | Scan Name. | +| TenableSC.ScanResults.Description | Unknown | Scan description. | +| TenableSC.ScanResults.ID | Unknown | Scan results ID. | + +### tenable-sc-get-scan-report + +*** +Requires security manager authentication. Get a single report with Tenable.sc scan results. + +#### Base Command + +`tenable-sc-get-scan-report` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| scan_results_id | Scan results ID. | Required | +| vulnerability_severity | Comma separated list of severity values of vulnerabilities to retrieve. Default is Critical,High,Medium,Low,Info. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.ScanResults.ID | number | Scan results ID. | +| TenableSC.ScanResults.Name | string | Scan name. | +| TenableSC.ScanResults.Status | string | Scan status. | +| TenableSC.ScanResults.ScannedIPs | number | Scan number of scanned IPs. | +| TenableSC.ScanResults.StartTime | date | Scan start time. | +| TenableSC.ScanResults.EndTime | date | Scan end time. | +| TenableSC.ScanResults.Checks | number | Scan completed checks. | +| TenableSC.ScanResults.RepositoryName | string | Scan repository name. | +| TenableSC.ScanResults.Description | string | Scan description. | +| TenableSC.ScanResults.Vulnerability.ID | number | Scan vulnerability ID. | +| TenableSC.ScanResults.Vulnerability.Name | string | Scan vulnerability Name. | +| TenableSC.ScanResults.Vulnerability.Family | string | Scan vulnerability family. | +| TenableSC.ScanResults.Vulnerability.Severity | string | Scan vulnerability severity. | +| TenableSC.ScanResults.Vulnerability.Total | number | Scan vulnerability total hosts. | +| TenableSC.ScanResults.Policy | string | Scan policy. | +| TenableSC.ScanResults.Group | string | Scan owner group name. | +| TenableSC.ScanResults.Owner | string | Scan owner user name. | +| TenableSC.ScanResults.Duration | number | Scan duration in minutes. | +| TenableSC.ScanResults.ImportTime | date | Scan import time. | + +### tenable-sc-list-credentials + +*** +Requires security manager authentication. Get a list of Tenable.sc credentials. + +#### Base Command + +`tenable-sc-list-credentials` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| manageable | Whether to return only manageable scan credentials. Returns both usable and manageable by default. Possible values are: true, false. Default is false. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.Credential.Name | string | Credential name. | +| TenableSC.Credential.ID | number | Credential ID. | +| TenableSC.Credential.Description | string | Credential description. | +| TenableSC.Credential.Type | string | Credential type. | +| TenableSC.Credential.Tag | string | Credential tag. | +| TenableSC.Credential.Group | string | Credential owner group name. | +| TenableSC.Credential.Owner | string | Credential owner user name. | +| TenableSC.Credential.LastModified | date | Credential last modified time. | + +### tenable-sc-list-policies + +*** +Requires security manager authentication. Get a list of Tenable.sc scan policies. + +#### Base Command + +`tenable-sc-list-policies` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| manageable | Whether to return only manageable scan policies. Returns both usable and manageable by default. Possible values are: true, false. Default is false. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.ScanPolicy.Name | string | Scan policy name. | +| TenableSC.ScanPolicy.ID | number | Scan policy ID. | +| TenableSC.ScanPolicy.Description | string | Scan policy description. | +| TenableSC.ScanPolicy.Tag | string | Scan policy tag. | +| TenableSC.ScanPolicy.Group | string | Scan policy owner group name. | +| TenableSC.ScanPolicy.Owner | string | Scan policy owner user name. | +| TenableSC.ScanPolicy.LastModified | date | Scan policy last modified time. | +| TenableSC.ScanPolicy.Type | string | Scan policy type. | + +### tenable-sc-list-report-definitions + +*** +Requires security manager authentication. Get a list of Tenable.sc report definitions. + +#### Base Command + +`tenable-sc-list-report-definitions` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| manageable | Whether to return only manageable reports. Returns both usable and manageable by default. Possible values are: true, false. Default is false. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.ReportDefinition.Name | string | Report definition name. | +| TenableSC.ReportDefinition.ID | number | Report definition ID. | +| TenableSC.ReportDefinition.Description | string | Report definition description. | +| TenableSC.ReportDefinition.Type | string | Report definition type. | +| TenableSC.ReportDefinition.Group | string | Report definition owner group name. | +| TenableSC.ReportDefinition.Owner | string | Report definition owner user name. | + +### tenable-sc-list-repositories + +*** +Requires security manager authentication. Get a list of Tenable.sc scan repositories. + +#### Base Command + +`tenable-sc-list-repositories` + +#### Input + +There are no input arguments for this command. + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.ScanRepository.Name | string | Scan Repository name. | +| TenableSC.ScanRepository.ID | number | Scan Repository ID. | +| TenableSC.ScanRepository.Description | string | Scan Repository. | + +### tenable-sc-list-zones + +*** +Requires admin authentication. Get a list of Tenable.sc scan zones. + +#### Base Command + +`tenable-sc-list-zones` + +#### Input + +There are no input arguments for this command. + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.ScanZone.Name | string | Scan Zone name. | +| TenableSC.ScanZone.ID | number | Scan Zone ID. | +| TenableSC.ScanZone.Description | string | Scan Zone description. | +| TenableSC.ScanZone.IPList | unknown | Scan Zone IP list. | +| TenableSC.ScanZone.ActiveScanners | number | Scan Zone active scanners. | +| TenableSC.ScanZone.Scanner.Name | string | Scanner name. | +| TenableSC.ScanZone.Scanner.ID | number | Scanner ID. | +| TenableSC.ScanZone.Scanner.Description | string | Scanner description. | +| TenableSC.ScanZone.Scanner.Status | number | Scanner status. | + +### Requires security manager authentication. tenable-sc-create-scan + +*** +Create a scan on Tenable.sc + +#### Base Command + +`Requires security manager authentication. tenable-sc-create-scan` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| name | Scan name. | Required | +| policy_id | Policy ID, can be retrieved from list-policies command. | Required | +| plugin_id | Plugin ID. | Optional | +| description | Scan description. | Optional | +| repository_id | Scan Repository ID, can be retrieved from list-repositories command. | Required | +| zone_id | Scan zone ID (default is all zones), can be retrieved from list-zones command. | Optional | +| schedule | Schedule for the scan. Possible values are: dependent, ical, never, rollover, now. | Optional | +| asset_ids | Either all assets or comma separated asset IDs to scan, can be retrieved from list-assets command. Possible values are: All, AllManageable. | Optional | +| scan_virtual_hosts | Whether to includes virtual hosts, default false. Possible values are: true, false. | Optional | +| ip_list | Comma separated IPs to scan e.g 10.0.0.1,10.0.0.2 . | Optional | +| report_ids | Comma separated list of report definition IDs to create post-scan, can be retrieved from list-report-definitions command. | Optional | +| credentials | Comma separated credentials IDs to use, can be retrieved from list-credentials command. | Optional | +| timeout_action | Scan timeout action, default is import. Possible values are: discard, import, rollover. | Optional | +| max_scan_time | Maximum scan run time in hours, default is 1. | Optional | +| dhcp_tracking | Track hosts which have been issued new IP address, (e.g. DHCP). Possible values are: true, false. | Optional | +| rollover_type | Scan rollover type. Possible values are: nextDay. | Optional | +| dependent_id | Dependent scan ID in case of a dependent schedule, can be retrieved from list-scans command. | Optional | +| time_zone | The timezone for the given start_time, possible values can be found here: https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html. | Optional | +| start_time | The scan start time, should be in the format of YYYY-MM-DD:HH:MM:SS or relative timestamp (i.e now, 3 days). | Optional | +| repeat_rule_freq | to specify repeating events based on an interval of a repeat_rule_freq or more. Possible values are: HOURLY, DAILY, WEEKLY, MONTHLY, YEARLY. | Optional | +| repeat_rule_interval | the number of repeat_rule_freq between each interval (for example: If repeat_rule_freq=DAILY and repeat_rule_interval=8 it means every eight days.). | Optional | +| repeat_rule_by_day | A comma-separated list of days of the week to run the schedule at. Possible values are: SU, MO, TU, WE, TH, FR, SA. | Optional | +| enabled | The "enabled" field can only be set to "false" for schedules of type "ical". For all other schedules types, "enabled" is set to "true". Possible values are: true, false. Default is true. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.Scan.ID | string | Scan ID. | +| TenableSC.Scan.CreatorID | string | Scan's creator ID. | +| TenableSC.Scan.Name | string | Scan Name. | +| TenableSC.Scan.Type | string | Scan type. | +| TenableSC.Scan.CreatedTime | date | Scan creation time. | +| TenableSC.Scan.OwnerName | string | Scan owner Username. | +| TenableSC.Scan.Reports | unknown | Scan report definition IDs. | + +### tenable-sc-delete-scan + +*** +Requires security manager authentication. Delete a scan in Tenable.sc + +#### Base Command + +`tenable-sc-delete-scan` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| scan_id | Scan ID, can be. retrieved from the list-scans command. | Required | + +#### Context Output + +There is no context output for this command. +### tenable-sc-list-assets + +*** +Requires security manager authentication. Get a list of Tenable.sc Assets. + +#### Base Command + +`tenable-sc-list-assets` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| manageable | Whether to return only manageable assets. Returns both usable and manageable by default. Possible values are: true, false. Default is false. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.Asset.ID | string | Asset ID. | +| TenableSC.Asset.Name | string | Asset Name. | +| TenableSC.Asset.HostCount | number | Asset host IPs count. | +| TenableSC.Asset.Type | string | Asset type. | +| TenableSC.Asset.Tag | string | Asset tag. | +| TenableSC.Asset.Owner | string | Asset owner username. | +| TenableSC.Asset.Group | string | Asset group. | +| TenableSC.Asset.LastModified | date | Asset last modified time. | + +### tenable-sc-create-asset + +*** +Requires security manager authentication. Create an Asset in Tenable.sc with provided IP addresses. + +#### Base Command + +`tenable-sc-create-asset` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| name | Asset Name. | Required | +| description | Asset description. | Optional | +| owner_id | Asset owner ID, default is the Session User ID, can be retrieved from the list-users command. | Optional | +| tag | Asset tag. | Optional | +| ip_list | Comma separated list of IPs to include in the asset, e.g 10.0.0.2,10.0.0.4. | Required | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.Asset.Name | string | Asset Name. | +| TenableSC.Asset.ID | string | Asset ID. | +| TenableSC.Asset.OwnerName | string | Asset owner name. | +| TenableSC.Asset.Tags | string | Asset tags. | + +### tenable-sc-get-asset + +*** +Requires security manager authentication. Get details for a given asset in Tenable.sc. + +#### Base Command + +`tenable-sc-get-asset` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| asset_id | Asset ID that can be retrieved from the list-assets command. | Required | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.Asset.ID | number | Asset ID. | +| TenableSC.Asset.Name | string | Asset name. | +| TenableSC.Asset.Description | string | Asset description. | +| TenableSC.Asset.Tag | string | Asset tag. | +| TenableSC.Asset.Modified | date | Asset last modified time. | +| TenableSC.Asset.Owner | string | Asset owner user name. | +| TenableSC.Asset.Group | string | Asset owner group. | +| TenableSC.Asset.IPs | unknown | Asset viewable IPs. | + +### tenable-sc-delete-asset + +*** +Requires security manager authentication. Delete the Asset with the given ID from Tenable.sc. + +#### Base Command + +`tenable-sc-delete-asset` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| asset_id | Asset ID. | Required | + +#### Context Output + +There is no context output for this command. +### tenable-sc-list-alerts + +*** +Requires security manager authentication. List alerts from Tenable.sc. + +#### Base Command + +`tenable-sc-list-alerts` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| manageable | Whether to return only manageable alerts. Returns both usable and manageable by default. Possible values are: true, false. Default is false. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.Alert.ID | string | Alert ID. | +| TenableSC.Alert.Name | string | Alert name. | +| TenableSC.Alert.Description | string | Alert description. | +| TenableSC.Alert.State | string | Alert state. | +| TenableSC.Alert.Actions | string | Alert Actions. | +| TenableSC.Alert.LastTriggered | date | Alert last triggered time. | +| TenableSC.Alert.LastEvaluated | date | Alert last evaluated time. | +| TenableSC.Alert.Group | string | Alert owner group name. | +| TenableSC.Alert.Owner | string | Alert owner user name. | + +### tenable-sc-get-alert + +*** +Requires security manager authentication. Get information about a given alert in Tenable.sc. + +#### Base Command + +`tenable-sc-get-alert` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| alert_id | Alert ID, can be retrieved from list-alerts command. | Required | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.Alert.ID | string | Alert ID. | +| TenableSC.Alert.Name | string | Alert name. | +| TenableSC.Alert.Description | string | Alert description. | +| TenableSC.Alert.State | string | Alert state. | +| TenableSC.Alert.Condition.Trigger | string | Alert trigger. | +| TenableSC.Alert.LastTriggered | date | Alert last triggered time. | +| TenableSC.Alert.Condition.Query | string | Alert query name. | +| TenableSC.Alert.Condition.Filter.Name | string | Alert query filter name. | +| TenableSC.Alert.Condition.Filter.Values | Unknown | Alert query filter values. | +| TenableSC.Alert.Action.Type | string | Alert action type. | +| TenableSC.Alert.Action.Values | Unknown | Alert action values. | + +### tenable-sc-get-device + +*** +Requires security manager authentication. Gets the specified device information. + +#### Base Command + +`tenable-sc-get-device` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| ip | A valid IP address of a device. | Optional | +| dns_name | DNS name of a device. | Optional | +| repository_id | Repository ID to get the device from, can be retrieved from list-repositories command. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.Device.IP | string | Device IP address. | +| TenableSC.Device.UUID | string | Device UUID. | +| TenableSC.Device.RepositoryID | string | Device repository ID. | +| TenableSC.Device.MacAddress | string | Device Mac address. | +| TenableSC.Device.NetbiosName | string | Device Netbios name. | +| TenableSC.Device.DNSName | string | Device DNS name. | +| TenableSC.Device.OS | string | Device Operating System. | +| TenableSC.Device.OsCPE | string | Device Common Platform Enumeration. | +| TenableSC.Device.LastScan | date | Device's last scan time. | +| TenableSC.Device.RepositoryName | string | Device repository name. | +| TenableSC.Device.TotalScore | number | Device total threat score. | +| TenableSC.Device.LowSeverity | number | Device total threat scores with low severity. | +| TenableSC.Device.MediumSeverity | number | Device total threat scores with medium severity. | +| TenableSC.Device.HighSeverity | number | Device total threat scores with high severity. | +| TenableSC.Device.CriticalSeverity | number | Device total threat scores with critical severity. | +| Endpoint.IPAddress | string | Endpoint IP address. | +| Endpoint.Hostname | string | Endpoint DNS name. | +| Endpoint.MACAddress | string | Endpoint mac address. | +| Endpoint.OS | string | Endpoint OS. | + +### tenable-sc-list-users + +*** +Results may vary based on the authentication type (admin or security manager). List users in Tenable.sc. + +#### Base Command + +`tenable-sc-list-users` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| id | Filter by user ID. | Optional | +| username | Filter by user username. | Optional | +| email | Filter by user email address. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.User.ID | string | User ID. | +| TenableSC.User.Username | string | Username. | +| TenableSC.User.FirstName | string | User first name. | +| TenableSC.User.LastName | string | User last name. | +| TenableSC.User.Title | string | User title. | +| TenableSC.User.Email | string | User email address. | +| TenableSC.User.Created | date | The creation time of the user. | +| TenableSC.User.Modified | date | Last modification time of the user. | +| TenableSC.User.Login | date | User last login. | +| TenableSC.User.Role | string | User role name. | + +### tenable-sc-get-system-licensing + +*** +Requires admin authentication. Retrieve licensing information from Tenable.sc + +#### Base Command + +`tenable-sc-get-system-licensing` + +#### Input + +There are no input arguments for this command. + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.Status.ActiveIPS | number | Number of active IP addresses. | +| TenableSC.Status.LicensedIPS | Unknown | Number of licensed IP addresses. | +| TenableSC.Status.License | Unknown | License status. | + +### tenable-sc-get-system-information + +*** +Requires admin authentication. Get the system information and diagnostics from Tenable.sc. + +#### Base Command + +`tenable-sc-get-system-information` + +#### Input + +There are no input arguments for this command. + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.System.Version | string | System version. | +| TenableSC.System.BuildID | string | System build ID. | +| TenableSC.System.ReleaseID | string | System release ID. | +| TenableSC.System.License | string | System license status. | +| TenableSC.System.JavaStatus | boolean | Server java status. | +| TenableSC.System.RPMStatus | boolean | Server RPM status. | +| TenableSC.System.DiskStatus | boolean | Server disk status. | +| TenableSC.System.DiskThreshold | number | System left space on disk. | +| TenableSC.System.LastCheck | date | System last check time. | + +### tenable-sc-get-all-scan-results + +*** +Requires security manager authentication. Returns all scan results in Tenable.sc. + +#### Base Command + +`tenable-sc-get-all-scan-results` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| manageable | Filter only manageable alerts. By default, returns both usable and manageable alerts. Possible values are: true, false. Default is false. | Optional | +| page | The page to return, starting from 0. Default is 0. | Optional | +| limit | The number of objects to return in one response (maximum limit is 200). Default is 50. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.ScanResults.ID | Number | Scan ID. | +| TenableSC.ScanResults.Name | string | Scan name. | +| TenableSC.ScanResults.Status | string | Scan status. | +| TenableSC.ScanResults.Description | string | Scan description. | +| TenableSC.ScanResults.Policy | string | Scan policy. | +| TenableSC.ScanResults.Group | string | Scan group name. | +| TenableSC.ScanResults.Checks | number | Scan completed number of checks. | +| TenableSC.ScanResults.StartTime | date | Scan results start time. | +| TenableSC.ScanResults.EndTime | date | Scan results end time. | +| TenableSC.ScanResults.Duration | number | Scan duration in minutes. | +| TenableSC.ScanResults.ImportTime | date | Scan import time. | +| TenableSC.ScanResults.ScannedIPs | number | Number of scanned IPs. | +| TenableSC.ScanResults.Owner | string | Scan owner name. | +| TenableSC.ScanResults.RepositoryName | string | Scan repository name. | + +### tenable-sc-list-groups + +*** +Requires security manager authentication. list all groups. + +#### Base Command + +`tenable-sc-list-groups` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| show_users | Wether to show group member or not. Default is True. Possible values are: true, false. Default is true. | Optional | +| limit | The number of objects to return in one response. Default is 50. Default is 50. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.Group.Name | string | Group name. | +| TenableSC.Group.ID | number | Group ID. | +| TenableSC.Group.Description | string | Group description. | +| TenableSC.Group.Users.Firstname | string | Group's user's first name. | +| TenableSC.Group.Users.Lastname | string | Group's user's last name. | +| TenableSC.Group.Users.ID | string | Group's user's id. | +| TenableSC.Group.Users.UUID | string | Group's user's uuid. | +| TenableSC.Group.Users.Username | string | Group's user's user name. | + +### tenable-sc-create-user + +*** +This command can be executed with both authentication types (admin or security manager) based on the roll_id you with to choose. Creates a new user. + +#### Base Command + +`tenable-sc-create-user` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| first_name | The user's first name. | Optional | +| last_name | The user's last name. | Optional | +| user_name | The user's username. | Required | +| email | The user's email address. Required if email_notice is given. | Optional | +| address | The user's postal address. | Optional | +| phone | The user's phone number. | Optional | +| city | The city the user is living in. | Optional | +| state | The state the user is living in. | Optional | +| country | The country the user is living in. | Optional | +| locked | Default is False. Wether the user should be locked or not. Possible values are: true, false. Default is false. | Optional | +| email_notice | If different from None, a valid email address must be given. Possible values are: both, password, id, none. Default is none. | Optional | +| auth_type | Tenable (TNS). Lightweight Directory Access Protocol (LDAP). Security Assertion Markup Language (SAML). LDAP server or SAML authentication need to be configured in order to select LDAP or SAML. Possible values are: Ldap, legacy, linked, saml, tns. Default is tns. | Required | +| password | The user's password. Must be at least 3 characters. | Required | +| time_zone | The user timezone, possible values can be found here: https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html. | Optional | +| role_id | The user's role. Should be a number between 1 to 7. Role description: 1- Administrator, 2- Security Manager, 3-Security Analyst, 4-Vulnerability Analyst, 5-Executive, 6-Credential Manager, 7-Auditor. Only an Administrator can create Administrator accounts. Possible values are: 0, 1, 2, 3, 4, 5, 6, 7. | Required | +| must_change_password | When choosing LDAP or SAML auth types, 'must_change_password' must be set to False. For all other cases can be either True or False. Possible values are: false, true. Default is false. | Optional | +| managed_users_groups | Comma separated list of session user's role can manage group. Use tenable-sc-list-groups to get all available groups. Default is 0. | Optional | +| managed_objects_groups | Comma separated list of session user's role can manage group. Use tenable-sc-list-groups to get all available groups. Default is 0. | Optional | +| group_id | Default is 0. Valid group ID whose users can be managed by created user. | Required | +| responsible_asset_id | Default is 0. ID of a valid, usable, accessible asset. Use tenable-sc-list-assets to get all available assets. -1 is not set, 0 is all assets, and other numbers are asset id. | Required | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.User.Address | String | User address. | +| TenableSC.User.ApiKeys | Unknown | User api keys. | +| TenableSC.User.AuthType | String | User auth type. | +| TenableSC.User.CanManage | Boolean | User permissions. | +| TenableSC.User.CanUse | Boolean | User permissions. | +| TenableSC.User.City | String | User city of residence. | +| TenableSC.User.Country | String | User country of residence. | +| TenableSC.User.CreatedTime | Date | User creation time. | +| TenableSC.User.Email | String | User email address. | +| TenableSC.User.FailedLogins | String | User number of failed logins. | +| TenableSC.User.Fax | String | User fax. | +| TenableSC.User.Fingerprint | Unknown | User fingerprint. | +| TenableSC.User.Firstname | String | User first name. | +| TenableSC.User.group.Description | String | User group's description. | +| TenableSC.User.Group.ID | String | User group's id. | +| TenableSC.User.Group.Name | String | User group's name. | +| TenableSC.User.ID | String | User id. | +| TenableSC.User.LastLogin | String | User last login time. | +| TenableSC.User.LastLoginIP | String | User last login IP. | +| TenableSC.User.Lastname | String | User last name. | +| TenableSC.User.Ldap.Description | String | User ldap description. | +| TenableSC.User.Ldap.ID | Number | User ldap ID> | +| TenableSC.User.Ldap.Name | String | User ldap name. | +| TenableSC.User.LdapUsername | String | user ldap username. | +| TenableSC.User.Locked | String | if user is locked or not. | +| TenableSC.User.ManagedObjectsGroups.Description | String | User managed object groups description. | +| TenableSC.User.ManagedObjectsGroups.ID | String | User managed object groups id | +| TenableSC.User.ManagedObjectsGroups.Name | String | User managed object groups name. | +| TenableSC.User.ManagedUsersGroups.Description | String | User managed users groups description. | +| TenableSC.User.ManagedUsersGroups.ID | String | User managed users groups id. | +| TenableSC.User.ManagedUsersGroups.Name | String | User managed users groups name. | +| TenableSC.User.ModifiedTime | Date | User last modification time. | +| TenableSC.User.MustChangePassword | String | If user must change password. | +| TenableSC.User.Password | String | If user password is set. | +| TenableSC.User.Phone | String | User phone number. | +| TenableSC.User.Preferences.Name | String | User preferences name. | +| TenableSC.User.Preferences.Tag | String | User preferences tag. | +| TenableSC.User.Preferences.Value | String | User preferences value. | +| TenableSC.User.ResponsibleAsset.Description | String | User responsible asset description. | +| TenableSC.User.ResponsibleAsset.ID | String | User responsible asset id. | +| TenableSC.User.ResponsibleAsset.Name | String | User responsible asset name. | +| TenableSC.User.ResponsibleAsset.UUID | Unknown | User responsible asset UUID. | +| TenableSC.User.Role.Description | String | User tole description. | +| TenableSC.User.Role.ID | String | User role id. | +| TenableSC.User.Role.Name | String | User role name | +| TenableSC.User.State | String | User state. | +| TenableSC.User.Status | String | User status. | +| TenableSC.User.Title | String | User title. | +| TenableSC.User.Username | String | User username. | +| TenableSC.User.UUID | String | User UUID. | + +### tenable-sc-update-user + +*** +update user details by given user_id. + +#### Base Command + +`tenable-sc-update-user` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| first_name | The user's first name. | Optional | +| last_name | The user's last name. | Optional | +| user_name | The user's username. | Optional | +| email | The user's email address. Required if email_notice is given. | Optional | +| address | The user's postal address. | Optional | +| phone | The user's phone number. | Optional | +| city | The city the user is living in. | Optional | +| state | The state the user is living in. | Optional | +| country | The country the user is living in. | Optional | +| locked | Default is False. Wether the user should be locked or not. Possible values are: true, false. Default is false. | Optional | +| time_zone | The user timezone, possible values can be found here: https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html. | Optional | +| role_id | The user's role. Should be a number between 1 to 7. Role description: 1- Administrator, 2- Security Manager, 3-Security Analyst, 4-Vulnerability Analyst, 5-Executive, 6-Credential Manager, 7-Auditor. Only an Administrator can create Administrator accounts. Possible values are: 0, 1, 2, 3, 4, 5, 6, 7. | Optional | +| must_change_password | When choosing LDAP or SAML auth types, 'must_change_password' must be set to False. For all other cases can be either True or False. Possible values are: false, true. Default is false. | Optional | +| managed_users_groups | Comma separated list of session user's role can manage group. Use tenable-sc-list-groups to get all available groups. Default is 0. | Optional | +| managed_objects_groups | Comma separated list of session user's role can manage group. Use tenable-sc-list-groups to get all available groups. Default is 0. | Optional | +| group_id | Default is 0. Valid group ID whose users can be managed by created user. | Optional | +| responsible_asset_id | Default is 0. ID of a valid, usable, accessible asset. Use tenable-sc-list-assets to get all available assets. -1 is not set, 0 is all assets, and other numbers are asset id. | Optional | +| password | The new password to set. Must be given with current_password. Must be at least 3 characters. | Optional | +| current_password | This is admin/Security Manager password from instance parameters. required when attempting to change user's password. | Optional | +| user_id | The id of the user whose details we wish to update. | Required | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.User.Address | String | User address. | +| TenableSC.User.ApiKeys | Unknown | User api keys. | +| TenableSC.User.AuthType | String | User auth type. | +| TenableSC.User.CanManage | Boolean | User permissions. | +| TenableSC.User.CanUse | Boolean | User permissions. | +| TenableSC.User.City | String | User city of residence. | +| TenableSC.User.Country | String | User country of residence. | +| TenableSC.User.CreatedTime | Date | User creation time. | +| TenableSC.User.Email | String | User email address. | +| TenableSC.User.FailedLogins | String | User number of failed logins. | +| TenableSC.User.Fax | String | User fax. | +| TenableSC.User.Fingerprint | Unknown | User fingerprint. | +| TenableSC.User.Firstname | String | User first name. | +| TenableSC.User.group.Description | String | User group's description. | +| TenableSC.User.Group.ID | String | User group's id. | +| TenableSC.User.Group.Name | String | User group's name. | +| TenableSC.User.ID | String | User id. | +| TenableSC.User.LastLogin | String | User last login time. | +| TenableSC.User.LastLoginIP | String | User last login IP. | +| TenableSC.User.Lastname | String | User last name. | +| TenableSC.User.Ldap.Description | String | User ldap description. | +| TenableSC.User.Ldap.ID | Number | User ldap ID> | +| TenableSC.User.Ldap.Name | String | User ldap name. | +| TenableSC.User.LdapUsername | String | user ldap username. | +| TenableSC.User.Locked | String | if user is locked or not. | +| TenableSC.User.ManagedObjectsGroups.Description | String | User managed object groups description. | +| TenableSC.User.ManagedObjectsGroups.ID | String | User managed object groups id | +| TenableSC.User.ManagedObjectsGroups.Name | String | User managed object groups name. | +| TenableSC.User.ManagedUsersGroups.Description | String | User managed users groups description. | +| TenableSC.User.ManagedUsersGroups.ID | String | User managed users groups id. | +| TenableSC.User.ManagedUsersGroups.Name | String | User managed users groups name. | +| TenableSC.User.ModifiedTime | Date | User last modification time. | +| TenableSC.User.MustChangePassword | String | If user must change password. | +| TenableSC.User.Password | String | If user password is set. | +| TenableSC.User.Phone | String | User phone number. | +| TenableSC.User.Preferences.Name | String | User preferences name. | +| TenableSC.User.Preferences.Tag | String | User preferences tag. | +| TenableSC.User.Preferences.Value | String | User preferences value. | +| TenableSC.User.ResponsibleAsset.Description | String | User responsible asset description. | +| TenableSC.User.ResponsibleAsset.ID | String | User responsible asset id. | +| TenableSC.User.ResponsibleAsset.Name | String | User responsible asset name. | +| TenableSC.User.ResponsibleAsset.UUID | Unknown | User responsible asset UUID. | +| TenableSC.User.Role.Description | String | User tole description. | +| TenableSC.User.Role.ID | String | User role id. | +| TenableSC.User.Role.Name | String | User role name | +| TenableSC.User.State | String | User state. | +| TenableSC.User.Status | String | User status. | +| TenableSC.User.Title | String | User title. | +| TenableSC.User.Username | String | User username. | +| TenableSC.User.UUID | String | User UUID. | + +### tenable-sc-delete-user + +*** +This command can be executed with both authentication types (admin or security manager). Delete a user by given user_id. + +#### Base Command + +`tenable-sc-delete-user` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| user_id | The id of the user we want to delete. | Required | + +#### Context Output + +There is no context output for this command. +### tenable-sc-list-plugin-family + +*** +Requires security manager authentication. list plugin families / return information about a plugin family given ID. + +#### Base Command + +`tenable-sc-list-plugin-family` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| plugin_id | The id of the plugin we want to search. If given, other arguments will be ignored. | Optional | +| limit | Default is 50. The number of objects to return in one response (maximum limit is 200). Ignored when plugin_id is given. Default is 50. | Optional | +| is_active | default is none. none - both active and passive Plugin Families are returned. true - Only active Plugin Families will be returned. false - Only passive Plugin Families will be returned. Ignored when plugin_id is given. Possible values are: true, false. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.PluginFamily.ID | String | PluginFamily ID. | +| TenableSC.PluginFamily.Name | String | PluginFamily name. | +| TenableSC.PluginFamily.Count | String | Number of plugins in family. | +| TenableSC.PluginFamily.Plugins | String | The plugins list. | +| TenableSC.PluginFamily.Type | String | PluginFamily type. | + +### tenable-sc-create-policy + +*** +Requires security manager authentication. This command is prerequisite for creating remediation scan. creates policy. + +#### Base Command + +`tenable-sc-create-policy` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| policy_name | The name of the policy you wish to create. | Optional | +| policy_description | The description of the policy you wish to create. | Optional | +| policy_template_id | Default is 1. Policy template id. Default is 1. | Required | +| port_scan_range | Possible values: default, all or a comma separated list of values - 21,23,25,80,110. | Optional | +| tcp_scanner | Only possible if you are using Linux or FreeBSD. On Windows or macOS, the scanner does not do a TCP scan and instead uses the SYN scanner..If you enable this option, you can also set the syn_firewall_detection. Possible values are: no, yes. Default is no. | Optional | +| syn_scanner | Identifies open TCP ports on the target hosts. If you enable this option, you can also set the syn_firewall_detection option. Possible values are: no, yes. Default is yes. | Optional | +| udp_scanner | Enabling the UDP port scanner may dramatically increase the scan time and produce unreliable results. Consider using the netstat or SNMP port enumeration options instead if possible. Possible values are: no, yes. Default is no. | Optional | +| family_id | Can be retrieved from the result of tenable-sc-list-plugin-family command . | Required | +| plugins_id | Comma separated list of plugin_ids, Can be retrieved from the result of tenable-sc-list-plugin-family command with family_id as argument. | Required | +| syn_firewall_detection | Default is Automatic (normal). Rely on local port enumeration first before relying on network port scans. Possible values are: Automatic (normal), Do not detect RST rate limitation(soft), Ignore closed ports(aggressive), Disabled(softer). Default is Automatic (normal). | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.ScanPolicy.AuditFiles | Unknown | Policy audit files. | +| TenableSC.ScanPolicy.CanManage | String | Policy permissions. | +| TenableSC.ScanPolicy.CanUse | String | Policy permissions. | +| TenableSC.ScanPolicy.Context | String | Policy context. | +| TenableSC.ScanPolicy.CreatedTime | Date | Policy creation time. | +| TenableSC.ScanPolicy.Creator.Firstname | String | Policy creator first name. | +| TenableSC.ScanPolicy.Creator.ID | String | Policy creator ID. | +| TenableSC.ScanPolicy.Creator.Lastname | String | Policy creator last name. | +| TenableSC.ScanPolicy.Creator.Username | String | Policy creator user name. | +| TenableSC.ScanPolicy.Creator.UUID | String | Policy creator UUID. | +| TenableSC.ScanPolicy.Description | String | Policy description. | +| TenableSC.ScanPolicy.Families.Count | String | Policy number of families. | +| TenableSC.ScanPolicy.Families.ID | String | Policy family ID. | +| TenableSC.ScanPolicy.Families.Name | String | Policy family name. | +| TenableSC.ScanPolicy.Families.Plugins | Unknown | Policy family plugins. | +| TenableSC.ScanPolicy.GenerateXCCDFResults | String | Policy generated XCCDF results. | +| TenableSC.ScanPolicy.Groups | Unknown | Policy groups. | +| TenableSC.ScanPolicy.ID | String | Policy ID. | +| TenableSC.ScanPolicy.ModifiedTime | Date | Policy last modification time. | +| TenableSC.ScanPolicy.Name | String | Policy name. | +| TenableSC.ScanPolicy.Owner.Firstname | String | Policy owner first name. | +| TenableSC.ScanPolicy.Owner.ID | String | Policy owner ID. | +| TenableSC.ScanPolicy.Owner.Lastname | String | Policy owner last name. | +| TenableSC.ScanPolicy.Owner.Username | String | Policy owner user name. | +| TenableSC.ScanPolicy.Owner.UUID | String | Policy owner UUID. | +| TenableSC.ScanPolicy.OwnerGroup.Description | String | Policy owner group description. | +| TenableSC.ScanPolicy.OwnerGroup.ID | String | Policy owner group ID. | +| TenableSC.ScanPolicy.OwnerGroup.Name | String | Policy owner group name. | +| TenableSC.ScanPolicy.PolicyTemplate.Agent | String | Policy template agent. | +| TenableSC.ScanPolicy.PolicyTemplate.Description | String | Policy template description. | +| TenableSC.ScanPolicy.PolicyTemplate.ID | String | Policy template ID. | +| TenableSC.ScanPolicy.PolicyTemplate.Name | String | Policy template name. | +| TenableSC.ScanPolicy.Preferences.PortscanRange | String | Policy port scan range. | +| TenableSC.ScanPolicy.Preferences.SynFirewallDetection | String | Policy SYN Firewall detection. | +| TenableSC.ScanPolicy.Preferences.SynScanner | String | Policy SYN scanner. | +| TenableSC.ScanPolicy.Preferences.TcpScanner | String | Policy TCP scanner. | +| TenableSC.ScanPolicy.Preferences.UdpScanner | String | Policy UDP scanner. | +| TenableSC.ScanPolicy.Status | String | Policy status. | +| TenableSC.ScanPolicy.tags | String | Policy tags. | +| TenableSC.ScanPolicy.TargetGroup.Description | String | Policy target group description. | +| TenableSC.ScanPolicy.TargetGroup.ID | Number | Policy target group ID. | +| TenableSC.ScanPolicy.TargetGroup.Name | String | Policy target group name. | +| TenableSC.ScanPolicy.UUID | String | Policy UUID. | + +### tenable-sc-list-query + +*** +Requires security manager authentication. Lists queries. + +#### Base Command + +`tenable-sc-list-query` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| query_id | The id of the query we wish to search. | Optional | +| type | The query type to retrieve. When no type is set all queries are returned. Possible values are: alert, lce, mobile, ticket, user. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.Query.Manageable.BrowseColumns | String | Relevant only when query_id is not given. Manageable Query browse columns. | +| TenableSC.Query.Manageable.BrowseSortColumn | String | Relevant only when query_id is not given. Manageable Query browse sort column. | +| TenableSC.Query.Manageable.BrowseSortDirection | String | Relevant only when query_id is not given. Manageable Query browse sort direction. | +| TenableSC.Query.Manageable.CanManage | String | Relevant only when query_id is not given. Manageable Query permissions. | +| TenableSC.Query.Manageable.CanUse | String | Relevant only when query_id is not given. Manageable Query permissions. | +| TenableSC.Query.Manageable.Context | String | Relevant only when query_id is not given. Manageable Query context. | +| TenableSC.Query.Manageable.CreatedTime | Date | Relevant only when query_id is not given. Manageable Query creation time. | +| TenableSC.Query.Manageable.Creator.Firstname | String | Relevant only when query_id is not given. Manageable Query Creator first name. | +| TenableSC.Query.Manageable.Creator.ID | String | Relevant only when query_id is not given. Manageable Query Creator ID. | +| TenableSC.Query.Manageable.Creator.Lastname | String | Relevant only when query_id is not given. Manageable Query Creator last name. | +| TenableSC.Query.Manageable.Creator.Username | String | Relevant only when query_id is not given. Manageable Query Creator user name. | +| TenableSC.Query.Manageable.Creator.UUID | String | Relevant only when query_id is not given. Manageable Query Creator UUID. | +| TenableSC.Query.Manageable.Description | String | Relevant only when query_id is not given. Manageable Query description. | +| TenableSC.Query.Manageable.Filters.FilterName | String | Relevant only when query_id is not given. Manageable Query filter name. | +| TenableSC.Query.Manageable.Filters.Operator | String | Relevant only when query_id is not given. Manageable Query filter operator. | +| TenableSC.Query.Manageable.Filters.Value | String | Relevant only when query_id is not given. Manageable Query filter value | +| TenableSC.Query.Manageable.Groups | Unknown | Relevant only when query_id is not given. Manageable Query groups. | +| TenableSC.Query.Manageable.ID | String | Relevant only when query_id is not given. Manageable Query ID. | +| TenableSC.Query.Manageable.ModifiedTime | Date | Relevant only when query_id is not given. Manageable Query modification time. | +| TenableSC.Query.Manageable.Name | String | Relevant only when query_id is not given. Manageable Query name | +| TenableSC.Query.Manageable.Owner.Firstname | String | Relevant only when query_id is not given. Manageable Query owner first name. | +| TenableSC.Query.Manageable.Owner.ID | String | Relevant only when query_id is not given. Manageable Query owner ID. | +| TenableSC.Query.Manageable.Owner.Lastname | String | Relevant only when query_id is not given. Manageable Query owner last name. | +| TenableSC.Query.Manageable.Owner.Username | String | Relevant only when query_id is not given. Manageable Query owner user name. | +| TenableSC.Query.Manageable.Owner.UUID | String | Relevant only when query_id is not given. Manageable Query owner UUID. | +| TenableSC.Query.Manageable.OwnerGroup.Description | String | Relevant only when query_id is not given. Manageable Query owner group description. | +| TenableSC.Query.Manageable.OwnerGroup.ID | String | Relevant only when query_id is not given. Manageable Query owner group ID. | +| TenableSC.Query.Manageable.OwnerGroup.Name | String | Relevant only when query_id is not given. Manageable Query owner group name. | +| TenableSC.Query.Manageable.Status | String | Relevant only when query_id is not given. Manageable Query status | +| TenableSC.Query.Manageable.Tags | String | Relevant only when query_id is not given. Manageable Query tags | +| TenableSC.Query.Manageable.TargetGroup.Description | String | Relevant only when query_id is not given. Manageable Query target group description. | +| TenableSC.Query.Manageable.TargetGroup.ID | Number | Relevant only when query_id is not given. Manageable Query target group ID. | +| TenableSC.Query.Manageable.TargetGroup.Name | String | Relevant only when query_id is not given. Manageable Query target group name. | +| TenableSC.Query.Manageable.Tool | String | Relevant only when query_id is not given. Manageable Query tool. | +| TenableSC.Query.Manageable.Type | String | Relevant only when query_id is not given. Manageable Query type. | +| TenableSC.Query.Manageable.Filters.Value.Description | String | Relevant only when query_id is not given. Manageable Query filter value description. | +| TenableSC.Query.Manageable.Filters.Value.ID | String | Relevant only when query_id is not given. Manageable Query filter value ID. | +| TenableSC.Query.Manageable.Filters.Value.Name | String | Relevant only when query_id is not given. Manageable Query filter value name. | +| TenableSC.Query.Manageable.Filters.Value.Type | String | Relevant only when query_id is not given. Manageable Query filter value type. | +| TenableSC.Query.Manageable.Filters.Value.UUID | String | Relevant only when query_id is not given. Manageable Query filter value UUID | +| TenableSC.Query.Manageable.Filters | Unknown | Relevant only when query_id is not given. Manageable Query filters. | +| TenableSC.Query.Usable.BrowseColumns | String | Relevant only when query_id is not given. Usable Query browse columns. | +| TenableSC.Query.Usable.BrowseSortColumn | String | Relevant only when query_id is not given. Usable Query browse sort column. | +| TenableSC.Query.Usable.BrowseSortDirection | String | Relevant only when query_id is not given. Usable Query browse sort direction. | +| TenableSC.Query.Usable.CanManage | String | Relevant only when query_id is not given. Usable Query permissions. | +| TenableSC.Query.Usable.CanUse | String | Relevant only when query_id is not given. Usable Query permissions. | +| TenableSC.Query.Usable.Context | String | Relevant only when query_id is not given. Usable Query context. | +| TenableSC.Query.Usable.CreatedTime | Date | Relevant only when query_id is not given. Usable Query creation time. | +| TenableSC.Query.Usable.Creator.Firstname | String | Relevant only when query_id is not given. Usable Query Creator first name. | +| TenableSC.Query.Usable.Creator.ID | String | Relevant only when query_id is not given. Usable Query Creator ID. | +| TenableSC.Query.Usable.Creator.Lastname | String | Relevant only when query_id is not given. Usable Query Creator last name. | +| TenableSC.Query.Usable.Creator.Username | String | Relevant only when query_id is not given. Usable Query Creator user name. | +| TenableSC.Query.Usable.Creator.UUID | String | Relevant only when query_id is not given. Usable Query Creator UUID. | +| TenableSC.Query.Usable.Description | String | Relevant only when query_id is not given. Usable Query description. | +| TenableSC.Query.Usable.Filters.FilterName | String | Relevant only when query_id is not given. Usable Query filter name. | +| TenableSC.Query.Usable.Filters.Operator | String | Relevant only when query_id is not given. Usable Query filter operator. | +| TenableSC.Query.Usable.Filters.Value | String | Relevant only when query_id is not given. Usable Query filter value | +| TenableSC.Query.Usable.Groups | Unknown | Relevant only when query_id is not given. Usable Query groups. | +| TenableSC.Query.Usable.ID | String | Relevant only when query_id is not given. Usable Query ID. | +| TenableSC.Query.Usable.ModifiedTime | Date | Relevant only when query_id is not given. Usable Query modification time. | +| TenableSC.Query.Usable.Name | String | Relevant only when query_id is not given. Usable Query name | +| TenableSC.Query.Usable.Owner.Firstname | String | Relevant only when query_id is not given. Usable Query owner first name. | +| TenableSC.Query.Usable.Owner.ID | String | Relevant only when query_id is not given. Usable Query owner ID. | +| TenableSC.Query.Usable.Owner.Lastname | String | Relevant only when query_id is not given. Usable Query owner last name. | +| TenableSC.Query.Usable.Owner.Username | String | Relevant only when query_id is not given. Usable Query owner user name. | +| TenableSC.Query.Usable.Owner.UUID | String | Relevant only when query_id is not given. Usable Query owner UUID. | +| TenableSC.Query.Usable.OwnerGroup.Description | String | Relevant only when query_id is not given. Usable Query owner group description. | +| TenableSC.Query.Usable.OwnerGroup.ID | String | Relevant only when query_id is not given. Usable Query owner group ID. | +| TenableSC.Query.Usable.OwnerGroup.Name | String | Relevant only when query_id is not given. Usable Query owner group name. | +| TenableSC.Query.Usable.Status | String | Relevant only when query_id is not given. Usable Query status | +| TenableSC.Query.Usable.Tags | String | Relevant only when query_id is not given. Usable Query tags | +| TenableSC.Query.Usable.TargetGroup.Description | String | Relevant only when query_id is not given. Usable Query target group description. | +| TenableSC.Query.Usable.TargetGroup.ID | Number | Relevant only when query_id is not given. Usable Query target group ID. | +| TenableSC.Query.Usable.TargetGroup.Name | String | Relevant only when query_id is not given. Usable Query target group name. | +| TenableSC.Query.Usable.Tool | String | Relevant only when query_id is not given. Usable Query tool. | +| TenableSC.Query.Usable.Type | String | Relevant only when query_id is not given. Usable Query type. | +| TenableSC.Query.Usable.Filters.Value.Description | String | Relevant only when query_id is not given. Usable Query filter value description. | +| TenableSC.Query.Usable.Filters.Value.ID | String | Relevant only when query_id is not given. Usable Query filter value ID. | +| TenableSC.Query.Usable.Filters.Value.Name | String | Relevant only when query_id is not given. Usable Query filter value name. | +| TenableSC.Query.Usable.Filters.Value.Type | String | Relevant only when query_id is not given. Usable Query filter value type. | +| TenableSC.Query.Usable.Filters.Value.UUID | String | Relevant only when query_id is not given. Usable Query filter value UUID | +| TenableSC.Query.Usable.Filters | Unknown | Relevant only when query_id is not given. Usable Query filters. | +| TenableSC.Query.BrowseColumns | String | Relevant only when query_id is given. Query browse columns. | +| TenableSC.Query.BrowseSortColumn | String | Relevant only when query_id is given. Query browse sort columns. | +| TenableSC.Query.BrowseSortDirection | String | Relevant only when query_id is given. Query browse sort direction | +| TenableSC.Query.CanManage | String | Relevant only when query_id is given. Query permissions. | +| TenableSC.Query.CanUse | String | Relevant only when query_id is given. Query permissions. | +| TenableSC.Query.Context | String | Relevant only when query_id is given. Query context. | +| TenableSC.Query.CreatedTime | Date | Relevant only when query_id is given. Query creation time. | +| TenableSC.Query.Creator.Firstname | String | Relevant only when query_id is given. Query creator first name. | +| TenableSC.Query.Creator.ID | String | Relevant only when query_id is given. Query creator ID. | +| TenableSC.Query.Creator.Lastname | String | Relevant only when query_id is given. Query creator last name. | +| TenableSC.Query.Creator.Username | String | Relevant only when query_id is given. Query creator user name. | +| TenableSC.Query.Creator.UUID | String | Relevant only when query_id is given. Query creator UUID. | +| TenableSC.Query.Description | String | Relevant only when query_id is given. Query description. | +| TenableSC.Query.Filters | Unknown | Relevant only when query_id is given. Query filters. | +| TenableSC.Query.Groups | Unknown | Relevant only when query_id is given. Query groups. | +| TenableSC.Query.ID | String | Relevant only when query_id is given. Query ID. | +| TenableSC.Query.ModifiedTime | Date | Relevant only when query_id is given. Query modification time. | +| TenableSC.Query.Name | String | Relevant only when query_id is given. Query name. | +| TenableSC.Query.Owner.Firstname | String | Relevant only when query_id is given. Query owner first name. | +| TenableSC.Query.Owner.ID | String | Relevant only when query_id is given. Query owner ID. | +| TenableSC.Query.Owner.Lastname | String | Relevant only when query_id is given. Query owner last name. | +| TenableSC.Query.Owner.Username | String | Relevant only when query_id is given. Query owner user name. | +| TenableSC.Query.Owner.UUID | String | Relevant only when query_id is given. Query owner UUID. | +| TenableSC.Query.OwnerGroup.Description | String | Relevant only when query_id is given. Query owner group description. | +| TenableSC.Query.OwnerGroup.ID | String | Relevant only when query_id is given. Query owner group ID. | +| TenableSC.Query.OwnerGroup.Name | String | Relevant only when query_id is given. Query owner group name. | +| TenableSC.Query.Status | String | Relevant only when query_id is given. Query status. | +| TenableSC.Query.Tags | String | Relevant only when query_id is given. Query tags. | +| TenableSC.Query.TargetGroup.Description | String | Relevant only when query_id is given. Query target group description. | +| TenableSC.Query.TargetGroup.ID | Number | Relevant only when query_id is given. Query target group ID. | +| TenableSC.Query.TargetGroup.Name | String | Relevant only when query_id is given. Query target group name. | +| TenableSC.Query.Tool | String | Relevant only when query_id is given. Query tool | +| TenableSC.Query.Type | String | Relevant only when query_id is given. Query type. | + +### tenable-sc-update-asset + +*** +Requires security manager authentication. Update an asset. + +#### Base Command + +`tenable-sc-update-asset` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| name | Asset name. | Optional | +| asset_id | The ID of the asset we wish to update. | Required | +| description | The asset description. | Optional | +| owner_id | The asset owner ID. | Optional | +| tag | The asset tag. | Optional | +| ip_list | Comma separated list of the asset IPs list. | Optional | + +#### Context Output + +There is no context output for this command. +### tenable-sc-create-remediation-scan + +*** +Requires security manager authentication. This command is prerequisite for creating remediation scan. creates policy. + +#### Base Command + +`tenable-sc-create-remediation-scan` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| policy_name | The name of the policy you wish to create. | Optional | +| policy_description | The description of the policy you wish to create. | Optional | +| port_scan_range | Possible values: default, all or a comma separated list of values - 21,23,25,80,110. | Optional | +| tcp_scanner | Only possible if you are using Linux or FreeBSD. On Windows or macOS, the scanner does not do a TCP scan and instead uses the SYN scanner..If you enable this option, you can also set the syn_firewall_detection. Possible values are: no, yes. Default is no. | Optional | +| syn_scanner | Identifies open TCP ports on the target hosts. If you enable this option, you can also set the syn_firewall_detection option. Possible values are: no, yes. Default is yes. | Optional | +| udp_scanner | Enabling the UDP port scanner may dramatically increase the scan time and produce unreliable results. Consider using the netstat or SNMP port enumeration options instead if possible. Possible values are: no, yes. Default is no. | Optional | +| syn_firewall_detection | Default is Automatic (normal). Rely on local port enumeration first before relying on network port scans. Possible values are: Automatic (normal), Do not detect RST rate limitation(soft), Ignore closed ports(aggressive), Disabled(softer). Default is Automatic (normal). | Optional | +| family_id | Can be retrieved from the result of tenable-sc-list-plugin-family command . | Required | +| plugins_id | Comma separated list of plugin_ids, Can be retrieved from the result of tenable-sc-list-plugin-family command with family_id as argument. | Required | +| scan_name | Scan name. | Required | +| description | Scan description. | Optional | +| repository_id | Default is 1. Scan Repository ID, can be retrieved from list-repositories command. Default is 1. | Required | +| time_zone | The timezone for the given start_time, possible values can be found here: https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html. | Optional | +| start_time | The scan start time, should be in the format of YYYY-MM-DD:HH:MM:SS or relative timestamp (i.e now, 3 days). | Optional | +| repeat_rule_freq | to specify repeating events based on an interval of a repeat_rule_freq or more. Possible values are: HOURLY, DAILY, WEEKLY, MONTHLY, YEARLY. | Optional | +| repeat_rule_interval | the number of repeat_rule_freq between each interval (for example: If repeat_rule_freq=DAILY and repeat_rule_interval=8 it means every eight days.). | Optional | +| repeat_rule_by_day | A comma-separated list of days of the week to run the schedule at. Possible values are: SU, MO, TU, WE, TH, FR, SA. | Optional | +| asset_ids | Either no assets or comma separated asset IDs to scan, can be retrieved from list-assets command. | Optional | +| scan_virtual_hosts | Default is false. Whether to includes virtual hosts, default false. Possible values are: true, false. | Optional | +| ip_list | Comma separated IPs to scan e.g 10.0.0.1,10.0.0.2 . | Optional | +| report_ids | Comma separated list of report definition IDs to create post-scan, can be retrieved from list-report-definitions command. | Optional | +| credentials | Comma separated credentials IDs to use, can be retrieved from list-credentials command. | Optional | +| timeout_action | Default is import. Default. discard - do not import any of the results obtained by the scan to the database. import - Import the results of the current scan and discard the information for any unscanned targets. rollover-Import the results from the scan into the database and create a rollover scan that may be launched at a later time to complete the scan. Possible values are: discard, import, rollover. Default is import. | Optional | +| max_scan_time | Maximum scan run time in hours, default is 1. | Optional | +| dhcp_tracking | Default is false. Track hosts which have been issued new IP address, (e.g. DHCP). Possible values are: true, false. | Optional | +| enabled | The "enabled" field can only be set to "false" for schedules of type "ical". For all other schedules types, "enabled" is set to "true". Possible values are: true, false. Default is true. | Optional | +| rollover_type | Default is nextDay. Create a rollover scan scheduled to launch the next day at the same start time as the just completed scan. template-Create a rollover scan as a template for users to launch manually This field is required if the timeout_action is set to rollover. Default is nextDay. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.Scan.Assets | Unknown | Scan assets. | +| TenableSC.Scan.CanManage | String | Scan permissions. | +| TenableSC.Scan.CanUse | String | Scan permissions. | +| TenableSC.Scan.ClassifyMitigatedAge | String | Scan classify mitigated age. | +| TenableSC.Scan.CreatedTime | Date | Scan creation time. | +| TenableSC.Scan.Creator.Firstname | String | Scan creator first name. | +| TenableSC.Scan.Creator.ID | String | Scan creator ID. | +| TenableSC.Scan.Creator.Lastname | String | Scan creator last name. | +| TenableSC.Scan.Creator.Username | String | Scan creator user name. | +| TenableSC.Scan.Creator.UUID | String | Scan creator UUID. | +| TenableSC.Scan.Credentials | Unknown | Scan credentials. | +| TenableSC.Scan.Description | String | Scan description. | +| TenableSC.Scan.DhcpTracking | String | Scan DHCP tracking. | +| TenableSC.Scan.EmailOnFinish | String | Scan email on finish. | +| TenableSC.Scan.EmailOnLaunch | String | Scan email on launch. | +| TenableSC.Scan.ID | String | Scan ID. | +| TenableSC.Scan.IpList | String | Scan IP list. | +| TenableSC.Scan.MaxScanTime | String | Scan max scan time. | +| TenableSC.Scan.ModifiedTime | Date | Scan last modification time. | +| TenableSC.Scan.Name | String | Scan name. | +| TenableSC.Scan.NumDependents | Number | Scan number of dependents. | +| TenableSC.Scan.Owner.Firstname | String | Scan owner first name. | +| TenableSC.Scan.Owner.ID | String | Scan owner ID. | +| TenableSC.Scan.Owner.Lastname | String | Scan owner last name. | +| TenableSC.Scan.Owner.Username | String | Scan owner user name. | +| TenableSC.Scan.Owner.UUID | String | Scan owner UUID. | +| TenableSC.Scan.OwnerGroup.Description | String | Scan owner group description. | +| TenableSC.Scan.OwnerGroup.ID | String | Scan owner group ID. | +| TenableSC.Scan.OwnerGroup.Name | String | Scan owner group name. | +| TenableSC.Scan.Plugin.Description | String | Scan plugin description. | +| TenableSC.Scan.Plugin.ID | String | Scan plugin ID. | +| TenableSC.Scan.Plugin.Name | String | Scan plugin name. | +| TenableSC.Scan.Plugin.Type | String | Scan plugin type. | +| TenableSC.Scan.Policy.Context | String | Scan policy context. | +| TenableSC.Scan.Policy.Description | String | Scan policy description. | +| TenableSC.Scan.Policy.ID | String | Scan policy ID. | +| TenableSC.Scan.Policy.Name | String | Scan policy name. | +| TenableSC.Scan.Policy.Owner.Firstname | String | Scan policy owner first name. | +| TenableSC.Scan.Policy.Owner.ID | String | Scan policy owner ID. | +| TenableSC.Scan.Policy.Owner.Lastname | String | Scan policy owner last name. | +| TenableSC.Scan.Policy.Owner.Username | String | Scan policy owner user name. | +| TenableSC.Scan.Policy.Owner.UUID | String | Scan policy owner UUID. | +| TenableSC.Scan.Policy.OwnerGroup.Description | String | Scan policy owner group description. | +| TenableSC.Scan.Policy.OwnerGroup.ID | String | Scan policy owner group ID. | +| TenableSC.Scan.Policy.OwnerGroup.Name | String | Scan policy owner group name. | +| TenableSC.Scan.Policy.Tags | String | Scan policy tags | +| TenableSC.Scan.Policy.UUID | String | Scan policy UUID. | +| TenableSC.Scan.PolicyPrefs.Name | String | Scan policy preferation name. | +| TenableSC.Scan.PolicyPrefs.Value | String | Scan policy preferation value. | +| TenableSC.Scan.Reports | Unknown | Scan reports. | +| TenableSC.Scan.Repository.Description | String | Scan repository description. | +| TenableSC.Scan.Repository.ID | String | Scan repository ID. | +| TenableSC.Scan.Repository.Name | String | Scan repository name. | +| TenableSC.Scan.Repository.Type | String | Scan repository type. | +| TenableSC.Scan.Repository.UUID | String | Scan repository UUID. | +| TenableSC.Scan.RolloverType | String | Scan rollover type. | +| TenableSC.Scan.ScanResultID | String | Scan results ID. | +| TenableSC.Scan.ScanningVirtualHosts | String | Scan virtual hosts | +| TenableSC.Scan.Schedule.Dependent.Description | String | Scan schedule dependent description. | +| TenableSC.Scan.Schedule.Dependent.ID | Number | Scan schedule dependent ID. | +| TenableSC.Scan.Schedule.Dependent.Name | String | Scan schedule dependent name. | +| TenableSC.Scan.Schedule.Enabled | String | Scan schedule enabled. | +| TenableSC.Scan.Schedule.ID | Number | Scan schedule ID. | +| TenableSC.Scan.Schedule.NextRun | Number | Scan schedule next run. | +| TenableSC.Scan.Schedule.ObjectType | Number | Scan schedule object type. | +| TenableSC.Scan.Schedule.RepeatRule | String | Scan schedule repeat rule. | +| TenableSC.Scan.Schedule.Start | String | Scan schedule start time. | +| TenableSC.Scan.Schedule.Type | String | Scan schedule type. | +| TenableSC.Scan.Status | String | Scan status. | +| TenableSC.Scan.TimeoutAction | String | Scan timeout action. | +| TenableSC.Scan.Type | String | Scan type. | +| TenableSC.Scan.UUID | String | Scan UUID. | +| TenableSC.Scan.Zone.Description | String | Scan zone description. | +| TenableSC.Scan.Zone.ID | Number | Scan zone ID. | +| TenableSC.Scan.Zone.Name | String | Scan zone name. | + +### tenable-sc-launch-scan-report + +*** +Requires security manager authentication. Polling command. Launch a scan by given scan ID, follow its status return a report when the scan is over. + +#### Base Command + +`tenable-sc-launch-scan-report` + +#### Input + +| **Argument Name** | **Description** | **Required** | +| --- | --- | --- | +| scan_id | The id of the scan we wish to get the report on. Can be retrieved from list-scans command. | Required | +| diagnostic_target | Valid IP/Hostname of a specific target to scan. Must be provided with diagnostic_password. | Optional | +| diagnostic_password | Valid password of the diagnostic_target. Must be provided with diagnostic_target. | Optional | +| scan_results_id | Deprecated. Scan results id. | Optional | +| timeout_in_seconds | Default is 3 hours. The timeout in seconds until polling ends. Default is 10800. | Optional | + +#### Context Output + +| **Path** | **Type** | **Description** | +| --- | --- | --- | +| TenableSC.ScanResults.ID | number | Scan results ID | +| TenableSC.ScanResults.Name | string | Scan name | +| TenableSC.ScanResults.Status | string | Scan status | +| TenableSC.ScanResults.ScannedIPs | number | Scan number of scanned IPs | +| TenableSC.ScanResults.StartTime | date | Scan start time | +| TenableSC.ScanResults.EndTime | date | Scan end time | +| TenableSC.ScanResults.Checks | number | Scan completed checks | +| TenableSC.ScanResults.RepositoryName | string | Scan repository name | +| TenableSC.ScanResults.Description | string | Scan description | +| TenableSC.ScanResults.Vulnerability.ID | number | Scan vulnerability ID | +| TenableSC.ScanResults.Vulnerability.Name | string | Scan vulnerability Name | +| TenableSC.ScanResults.Vulnerability.Family | string | Scan vulnerability family | +| TenableSC.ScanResults.Vulnerability.Severity | string | Scan vulnerability severity | +| TenableSC.ScanResults.Vulnerability.Total | number | Scan vulnerability total hosts | +| TenableSC.ScanResults.Policy | string | Scan policy | +| TenableSC.ScanResults.Group | string | Scan owner group name | +| TenableSC.ScanResults.Owner | string | Scan owner user name | +| TenableSC.ScanResults.Duration | number | Scan duration in minutes | +| TenableSC.ScanResults.ImportTime | date | Scan import time | From ce3194baa1fe38126f2decc47b4531a501f223a1 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Thu, 18 May 2023 17:44:41 +0300 Subject: [PATCH 029/115] pre-commit fixes --- Packs/Tenable_sc/.secrets-ignore | 3 ++- Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py | 2 +- Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml | 2 +- Packs/Tenable_sc/ReleaseNotes/1_0_10.md | 1 + 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Packs/Tenable_sc/.secrets-ignore b/Packs/Tenable_sc/.secrets-ignore index 06f1fde335c3..463a2fe55ae2 100644 --- a/Packs/Tenable_sc/.secrets-ignore +++ b/Packs/Tenable_sc/.secrets-ignore @@ -8,4 +8,5 @@ https://raw.githubusercontent.com e1C::c https://access.redhat.com 213.35.2.109 -https://user-images.githubusercontent.com \ No newline at end of file +https://user-images.githubusercontent.com +https://docs.oracle.com \ No newline at end of file diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index c8c49f70b8a3..11d3e227eb46 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -2642,7 +2642,7 @@ def update_asset_command(client: Client, args: Dict[str, Any]): asset_id = args.get('asset_id') res = client.update_asset(args, asset_id) if not res or not res.get('response', []): - return_error(f"Couldn't update asset {asset_id}") + return_error("Couldn't update asset.") return CommandResults( raw_response=res, diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index b6cb8ea0a43f..9672b8ecfe53 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -3122,7 +3122,7 @@ script: script: '-' type: python subtype: python3 - dockerimage: demisto/python3:3.10.6.33415 + dockerimage: demisto/python3:3.10.11.59070 tests: - tenable-sc-test fromversion: 5.0.0 diff --git a/Packs/Tenable_sc/ReleaseNotes/1_0_10.md b/Packs/Tenable_sc/ReleaseNotes/1_0_10.md index 602e7eb6232b..8180c13e25fc 100644 --- a/Packs/Tenable_sc/ReleaseNotes/1_0_10.md +++ b/Packs/Tenable_sc/ReleaseNotes/1_0_10.md @@ -5,6 +5,7 @@ - Improved implementation of the **tenable-sc-list-zones** command. - Added the following commands: **tenable-sc-list-groups**, **tenable-sc-create-user**, **tenable-sc-update-user**, **tenable-sc-delete-user**, **tenable-sc-list-plugin-family**, **tenable-sc-create-policy**, **enable-sc-list-query**, **tenable-sc-launch-scan-report**, **tenable-sc-create-remediation-scan**, **tenable-sc-update-asset**. For more information, refer to the integration readme. - Added the **Access key** and **Secret key** integration params to support a more secured instance configuration. pre-configured integration will not be affected by this change. +- Updated the Docker image to: *demisto/python3:3.10.11.59070*. #### Playbooks ##### Launch Scan - Tenable.sc From 60e115f7de7c3cf5eed4a1e18d763c0baa17a281 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Sun, 21 May 2023 16:22:02 +0300 Subject: [PATCH 030/115] adding unit tests --- .../Integrations/Tenable_sc/README.md | 121 ++++++++++++++++-- .../Integrations/Tenable_sc/Tenable_sc.py | 62 ++++----- .../Integrations/Tenable_sc/Tenable_sc.yml | 70 +++++----- .../Tenable_sc/Tenable_sc_description.md | 31 +++++ .../Tenable_sc/Tenable_sc_test.py | 102 +++++++++++++++ .../test_data/test_list_groups_command.json | 73 +++++++++++ .../test_data/test_list_zones_command.json | 91 +++++++++++++ .../test_data/test_update_asset_command.json | 51 ++++++++ 8 files changed, 525 insertions(+), 76 deletions(-) create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_description.md create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_groups_command.json create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_zones_command.json create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_update_asset_command.json diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/README.md b/Packs/Tenable_sc/Integrations/Tenable_sc/README.md index e093b25de10e..e83702cb79a3 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/README.md +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/README.md @@ -1,5 +1,14 @@ -With Tenable.sc (formerly SecurityCenter) you get a real-time, continuous assessment of your security posture so you can find and fix vulnerabilities faster. -This integration was integrated and tested with version xx of Tenable.sc +Use the Tenable.sc integration to get a real-time, continuous assessment of your security posture so you can find and fix vulnerabilities faster. +All data in Tenable.sc is managed using group level permissions. If you have several groups, data (scans, scan results, assets, etc) can be viewable but not manageable. Users with Security Manager role  can manage everything. These permissions come into play when multiple groups are in use. +It is important to know what data is manageable for the user in order to work with the integration. +This integration was integrated and tested with Tenable.sc v5.7.0. + +## Use cases: + * Create and run scans. + * Launch and manage scan results and the found vulnerabilities. + * Create and view assets. + * View policies, repositories, credentials, users and more system information. + * View and real-time receiving of alerts. ## Configure Tenable.sc on Cortex XSOAR @@ -7,18 +16,18 @@ This integration was integrated and tested with version xx of Tenable.sc 2. Search for Tenable.sc. 3. Click **Add instance** to create and configure a new integration instance. - | **Parameter** | **Required** | - | --- | --- | - | Server URL (e.g. https://192.168.0.1) | True | - | Access key | False | - | Secret key | False | - | Username | False | - | Password | False | - | Trust any certificate (not secure) | False | - | Use system proxy settings | False | - | Fetch incidents | False | - | Incident type | False | - | First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days, 3 months, 1 year) | False | + | **Parameter** | **Description** | **Required** | + | --- | --- | --- | + | Server URL (e.g. https://192.168.0.1) | The server URL. | True | + | Access key | follow the readme for steps to generate one. | False | + | Secret key | | False | + | Username | The Username is either admin or secman \(depend on the role you wish to log into\) and your password to the tenable server. | False | + | Password | | False | + | Trust any certificate (not secure) | | False | + | Use system proxy settings | | False | + | Fetch incidents | | False | + | Incident type | | False | + | First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days, 3 months, 1 year) | The timestamp to start the fetch from. | False | 4. Click **Test** to validate the URLs, token, and connection. @@ -53,6 +62,9 @@ Requires security manager authentication. Get a list of Tenable.sc existing scan | TenableSC.Scan.Group | string | Scan policy owner group name. | | TenableSC.Scan.Owner | string | Scan policy owner user name. | +#### Human Readable Output + + ### tenable-sc-launch-scan *** @@ -80,6 +92,8 @@ Requires security manager authentication. Launch an existing scan from Tenable.s | TenableSC.ScanResults.JobID | string | Job ID. | | TenableSC.ScanResults.Status | string | Scan status. | +#### Human Readable Output + ### tenable-sc-get-vulnerability *** @@ -130,6 +144,8 @@ Requires security manager authentication. Get details about a given vulnerabilit | TenableSC.ScanResults.Vulnerability.Host.Port | number | Vulnerability Host Port. | | TenableSC.ScanResults.Vulnerability.Host.Protocol | string | Vulnerability Host Protocol. | +#### Human Readable Output + ### tenable-sc-get-scan-status *** @@ -154,6 +170,8 @@ Requires security manager authentication. Get the status of a specific scan in T | TenableSC.ScanResults.Description | Unknown | Scan description. | | TenableSC.ScanResults.ID | Unknown | Scan results ID. | +#### Human Readable Output + ### tenable-sc-get-scan-report *** @@ -194,6 +212,8 @@ Requires security manager authentication. Get a single report with Tenable.sc sc | TenableSC.ScanResults.Duration | number | Scan duration in minutes. | | TenableSC.ScanResults.ImportTime | date | Scan import time. | +#### Human Readable Output + ### tenable-sc-list-credentials *** @@ -222,6 +242,8 @@ Requires security manager authentication. Get a list of Tenable.sc credentials. | TenableSC.Credential.Owner | string | Credential owner user name. | | TenableSC.Credential.LastModified | date | Credential last modified time. | +#### Human Readable Output + ### tenable-sc-list-policies *** @@ -250,6 +272,8 @@ Requires security manager authentication. Get a list of Tenable.sc scan policies | TenableSC.ScanPolicy.LastModified | date | Scan policy last modified time. | | TenableSC.ScanPolicy.Type | string | Scan policy type. | +#### Human Readable Output + ### tenable-sc-list-report-definitions *** @@ -276,6 +300,8 @@ Requires security manager authentication. Get a list of Tenable.sc report defini | TenableSC.ReportDefinition.Group | string | Report definition owner group name. | | TenableSC.ReportDefinition.Owner | string | Report definition owner user name. | +#### Human Readable Output + ### tenable-sc-list-repositories *** @@ -297,6 +323,8 @@ There are no input arguments for this command. | TenableSC.ScanRepository.ID | number | Scan Repository ID. | | TenableSC.ScanRepository.Description | string | Scan Repository. | +#### Human Readable Output + ### tenable-sc-list-zones *** @@ -324,6 +352,16 @@ There are no input arguments for this command. | TenableSC.ScanZone.Scanner.Description | string | Scanner description. | | TenableSC.ScanZone.Scanner.Status | number | Scanner status. | +#### Human Readable Output + +### Tenable.sc Scan Zones +|ID|Name|IPList|activeScanners| +|---|---|---|---| +| 1 | Default Scan Zone | ip | 1 | +### Tenable.sc Scanners\n|ID|Name|Status| +|---|---|---| +| 2 | RHEL6 Scanner | 1 | + ### Requires security manager authentication. tenable-sc-create-scan *** @@ -373,6 +411,8 @@ Create a scan on Tenable.sc | TenableSC.Scan.OwnerName | string | Scan owner Username. | | TenableSC.Scan.Reports | unknown | Scan report definition IDs. | +#### Human Readable Output + ### tenable-sc-delete-scan *** @@ -391,6 +431,8 @@ Requires security manager authentication. Delete a scan in Tenable.sc #### Context Output There is no context output for this command. + +#### Human Readable Output ### tenable-sc-list-assets *** @@ -419,6 +461,8 @@ Requires security manager authentication. Get a list of Tenable.sc Assets. | TenableSC.Asset.Group | string | Asset group. | | TenableSC.Asset.LastModified | date | Asset last modified time. | +#### Human Readable Output + ### tenable-sc-create-asset *** @@ -447,6 +491,8 @@ Requires security manager authentication. Create an Asset in Tenable.sc with pro | TenableSC.Asset.OwnerName | string | Asset owner name. | | TenableSC.Asset.Tags | string | Asset tags. | +#### Human Readable Output + ### tenable-sc-get-asset *** @@ -475,6 +521,8 @@ Requires security manager authentication. Get details for a given asset in Tenab | TenableSC.Asset.Group | string | Asset owner group. | | TenableSC.Asset.IPs | unknown | Asset viewable IPs. | +#### Human Readable Output + ### tenable-sc-delete-asset *** @@ -493,6 +541,8 @@ Requires security manager authentication. Delete the Asset with the given ID fro #### Context Output There is no context output for this command. + +#### Human Readable Output ### tenable-sc-list-alerts *** @@ -522,6 +572,8 @@ Requires security manager authentication. List alerts from Tenable.sc. | TenableSC.Alert.Group | string | Alert owner group name. | | TenableSC.Alert.Owner | string | Alert owner user name. | +#### Human Readable Output + ### tenable-sc-get-alert *** @@ -553,6 +605,8 @@ Requires security manager authentication. Get information about a given alert in | TenableSC.Alert.Action.Type | string | Alert action type. | | TenableSC.Alert.Action.Values | Unknown | Alert action values. | +#### Human Readable Output + ### tenable-sc-get-device *** @@ -594,6 +648,8 @@ Requires security manager authentication. Gets the specified device information. | Endpoint.MACAddress | string | Endpoint mac address. | | Endpoint.OS | string | Endpoint OS. | +#### Human Readable Output + ### tenable-sc-list-users *** @@ -626,6 +682,8 @@ Results may vary based on the authentication type (admin or security manager). L | TenableSC.User.Login | date | User last login. | | TenableSC.User.Role | string | User role name. | +#### Human Readable Output + ### tenable-sc-get-system-licensing *** @@ -647,6 +705,8 @@ There are no input arguments for this command. | TenableSC.Status.LicensedIPS | Unknown | Number of licensed IP addresses. | | TenableSC.Status.License | Unknown | License status. | +#### Human Readable Output + ### tenable-sc-get-system-information *** @@ -674,6 +734,8 @@ There are no input arguments for this command. | TenableSC.System.DiskThreshold | number | System left space on disk. | | TenableSC.System.LastCheck | date | System last check time. | +#### Human Readable Output + ### tenable-sc-get-all-scan-results *** @@ -710,6 +772,8 @@ Requires security manager authentication. Returns all scan results in Tenable.sc | TenableSC.ScanResults.Owner | string | Scan owner name. | | TenableSC.ScanResults.RepositoryName | string | Scan repository name. | +#### Human Readable Output + ### tenable-sc-list-groups *** @@ -739,6 +803,8 @@ Requires security manager authentication. list all groups. | TenableSC.Group.Users.UUID | string | Group's user's uuid. | | TenableSC.Group.Users.Username | string | Group's user's user name. | +#### Human Readable Output + ### tenable-sc-create-user *** @@ -828,6 +894,8 @@ This command can be executed with both authentication types (admin or security m | TenableSC.User.Username | String | User username. | | TenableSC.User.UUID | String | User UUID. | +#### Human Readable Output + ### tenable-sc-update-user *** @@ -917,6 +985,8 @@ update user details by given user_id. | TenableSC.User.Username | String | User username. | | TenableSC.User.UUID | String | User UUID. | +#### Human Readable Output + ### tenable-sc-delete-user *** @@ -935,6 +1005,8 @@ This command can be executed with both authentication types (admin or security m #### Context Output There is no context output for this command. + +#### Human Readable Output ### tenable-sc-list-plugin-family *** @@ -962,6 +1034,8 @@ Requires security manager authentication. list plugin families / return informat | TenableSC.PluginFamily.Plugins | String | The plugins list. | | TenableSC.PluginFamily.Type | String | PluginFamily type. | +#### Human Readable Output + ### tenable-sc-create-policy *** @@ -1034,6 +1108,8 @@ Requires security manager authentication. This command is prerequisite for creat | TenableSC.ScanPolicy.TargetGroup.Name | String | Policy target group name. | | TenableSC.ScanPolicy.UUID | String | Policy UUID. | +#### Human Readable Output + ### tenable-sc-list-query *** @@ -1170,6 +1246,8 @@ Requires security manager authentication. Lists queries. | TenableSC.Query.Tool | String | Relevant only when query_id is given. Query tool | | TenableSC.Query.Type | String | Relevant only when query_id is given. Query type. | +#### Human Readable Output + ### tenable-sc-update-asset *** @@ -1193,6 +1271,11 @@ Requires security manager authentication. Update an asset. #### Context Output There is no context output for this command. + +#### Human Readable Output + +asset was updated successfully. + ### tenable-sc-create-remediation-scan *** @@ -1314,6 +1397,8 @@ Requires security manager authentication. This command is prerequisite for creat | TenableSC.Scan.Zone.ID | Number | Scan zone ID. | | TenableSC.Scan.Zone.Name | String | Scan zone name. | +#### Human Readable Output + ### tenable-sc-launch-scan-report *** @@ -1356,3 +1441,11 @@ Requires security manager authentication. Polling command. Launch a scan by give | TenableSC.ScanResults.Owner | string | Scan owner user name | | TenableSC.ScanResults.Duration | number | Scan duration in minutes | | TenableSC.ScanResults.ImportTime | date | Scan import time | + +#### Human Readable Output + +#### Human Readable Output + + +## Troubleshooting +For errors within Tenable.sc, the cause is generally specified, e.g., The currently logged in used is not an administrator, Unable to retrieve Asset #2412. Asset #2412 does not exist or Invalid login credentials. However there might be connection errors, for example when the server URL provided is incorrect. \ No newline at end of file diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 11d3e227eb46..eaeea8851dac 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -1327,26 +1327,29 @@ def list_zones_command(client: Client, args: Dict[str, Any]): headers = ['ID', 'Name', 'Description', 'IPList', 'activeScanners'] mapped_zones = [{ - 'ID': z['id'], - 'Name': z['name'], - 'Description': z['description'], - 'IPList': z['ipList'], - 'activeScanners': z['activeScanners'] + 'ID': z.get('id', ""), + 'Name': z.get('name', ""), + 'Description': z.get('description', ""), + 'IPList': z.get('ipList', ""), + 'activeScanners': z.get('activeScanners', "") } for z in zones] hr = tableToMarkdown('Tenable.sc Scan Zones', mapped_zones, headers, removeNull=True) - mapped_scanners_total = [] + mapped_scanners_total, found_ids = [], [] for index, zone in enumerate(zones): if scanners := zone.get('scanners'): mapped_scanners = [{ - 'ID': s['id'], - 'Name': s['name'], - 'Description': s['description'], - 'Status': s['status'] - } for s in scanners] + 'ID': scanner['id'], + 'Name': scanner['name'], + 'Description': scanner['description'], + 'Status': scanner['status'] + } for scanner in scanners] mapped_zones[index]['Scanner'] = mapped_scanners - mapped_scanners_total.extend(mapped_scanners) + for scanner in mapped_scanners: + if scanner.get("ID") not in found_ids: + found_ids.append(scanner.get("ID")) + mapped_scanners_total.append(scanner) headers = ['ID', 'Name', 'Description', 'Status'] if mapped_scanners_total: @@ -2022,17 +2025,17 @@ def list_users_command(client: Client, args: Dict[str, Any]): ] mapped_users = [{ - 'ID': u['id'], - 'Username': u['username'], - 'FirstName': u['firstname'], - 'LastName': u['lastname'], - 'Title': u['title'], - 'Email': u['email'], - 'Created': timestamp_to_utc(u['createdTime']), - 'Modified': timestamp_to_utc(u['modifiedTime']), - 'LastLogin': timestamp_to_utc(u['lastLogin']), - 'Role': u['role'].get('name') - } for u in users] + 'ID': user['id'], + 'Username': user['username'], + 'FirstName': user['firstname'], + 'LastName': user['lastname'], + 'Title': user['title'], + 'Email': user['email'], + 'Created': timestamp_to_utc(user['createdTime']), + 'Modified': timestamp_to_utc(user['modifiedTime']), + 'LastLogin': timestamp_to_utc(user['lastLogin']), + 'Role': user['role'].get('name') + } for user in users] return CommandResults( outputs=createContext(mapped_users, removeNull=True), @@ -2330,7 +2333,7 @@ def list_groups_command(client: Client, args: Dict[str, Any]): groups = mapped_groups return CommandResults( - outputs=createContext(groups, removeNull=True), + outputs=createContext(response_to_context(groups), removeNull=True), outputs_prefix='TenableSC.Group', raw_response=res, outputs_key_field='ID', @@ -2451,7 +2454,7 @@ def process_update_and_create_user_response(res, hr_header): } return CommandResults( - outputs=createContext(response, removeNull=True), + outputs=createContext(response_to_context(response), removeNull=True), outputs_prefix='TenableSC.User', raw_response=res, outputs_key_field='ID', @@ -2505,7 +2508,7 @@ def list_plugin_family_command(client: Client, args: Dict[str, Any]): headers = ["Plugin ID", "Plugin Name", "Is Active"] return CommandResults( - outputs=createContext(plugins, removeNull=True, keyTransform=capitalize_first_letter), + outputs=createContext(response_to_context(plugins), removeNull=True), outputs_prefix='TenableSC.PluginFamily', raw_response=res, outputs_key_field='ID', @@ -2544,7 +2547,7 @@ def create_policy_command(client: Client, args: Dict[str, Any]): "policyTemplate Name"] return CommandResults( - outputs=createContext(created_policy, removeNull=True, keyTransform=capitalize_first_letter), + outputs=createContext(response_to_context(created_policy), removeNull=True), outputs_prefix='TenableSC.Query', raw_response=res, outputs_key_field='ID', @@ -2597,7 +2600,7 @@ def create_remediation_scan_command(client: Client, args: Dict[str, Any]): } return CommandResults( - outputs=createContext(scan, removeNull=True), + outputs=createContext(response_to_context(scan), removeNull=True), outputs_prefix='TenableSC.Scan', raw_response=res, outputs_key_field='ID', @@ -2622,7 +2625,7 @@ def list_query_command(client: Client, args: Dict[str, Any]): res, hr, ec = list_queries(client, type) return CommandResults( - outputs=createContext(ec, removeNull=True, keyTransform=capitalize_first_letter), + outputs=createContext(response_to_context(ec), removeNull=True), outputs_prefix='TenableSC.Query', raw_response=res, outputs_key_field='ID', @@ -2788,6 +2791,7 @@ def main(): ) if command == 'test-module': + list_users_command(client, args) demisto.results('ok') elif command == 'fetch-incidents': first_fetch = params.get('fetch_time').strip() diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index 9672b8ecfe53..7bbd203632f5 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -11,12 +11,14 @@ configuration: required: true type: 0 section: Connect + additionalinfo: The server URL. - display: Access key name: creds_keys required: false type: 9 displaypassword: Secret key section: Connect + additionalinfo: follow the readme for steps to generate one. - display: Username hiddenusername: true hiddenpassword: true @@ -24,6 +26,7 @@ configuration: required: false type: 9 section: Connect + additionalinfo: The Username is either admin or secman (depend on the role you wish to log into) and your password to the tenable server. - display: Trust any certificate (not secure) name: unsecure required: false @@ -52,6 +55,7 @@ configuration: required: false type: 0 section: Collect + additionalinfo: The timestamp to start the fetch from. description: With Tenable.sc (formerly SecurityCenter) you get a real-time, continuous assessment of your security posture so you can find and fix vulnerabilities faster. display: Tenable.sc name: Tenable.sc @@ -70,7 +74,7 @@ script: required: false secret: false deprecated: false - description: Requires security manager authentication. Get a list of Tenable.sc existing scans + description: Requires security manager role. Get a list of Tenable.sc existing scans execution: false name: tenable-sc-list-scans outputs: @@ -112,7 +116,7 @@ script: required: false secret: false deprecated: false - description: Requires security manager authentication. Launch an existing scan from Tenable.sc + description: Requires security manager role. Launch an existing scan from Tenable.sc execution: false name: tenable-sc-launch-scan outputs: @@ -195,7 +199,7 @@ script: required: false secret: false deprecated: false - description: Requires security manager authentication. Get details about a given vulnerability from a given Tenable.sc scan + description: Requires security manager role. Get details about a given vulnerability from a given Tenable.sc scan execution: false name: tenable-sc-get-vulnerability outputs: @@ -276,7 +280,7 @@ script: required: true secret: false deprecated: false - description: Requires security manager authentication. Get the status of a specific scan in Tenable.sc. + description: Requires security manager role. Get the status of a specific scan in Tenable.sc. execution: false name: tenable-sc-get-scan-status outputs: @@ -307,7 +311,7 @@ script: required: false secret: false deprecated: false - description: Requires security manager authentication. Get a single report with Tenable.sc scan results. + description: Requires security manager role. Get a single report with Tenable.sc scan results. execution: false name: tenable-sc-get-scan-report outputs: @@ -381,7 +385,7 @@ script: required: false secret: false deprecated: false - description: Requires security manager authentication. Get a list of Tenable.sc credentials. + description: Requires security manager role. Get a list of Tenable.sc credentials. execution: false name: tenable-sc-list-credentials outputs: @@ -422,7 +426,7 @@ script: required: false secret: false deprecated: false - description: Requires security manager authentication. Get a list of Tenable.sc scan policies. + description: Requires security manager role. Get a list of Tenable.sc scan policies. execution: false name: tenable-sc-list-policies outputs: @@ -463,7 +467,7 @@ script: required: false secret: false deprecated: false - description: Requires security manager authentication. Get a list of Tenable.sc report definitions. + description: Requires security manager role. Get a list of Tenable.sc report definitions. execution: false name: tenable-sc-list-report-definitions outputs: @@ -486,7 +490,7 @@ script: description: Report definition owner user name. type: string - deprecated: false - description: Requires security manager authentication. Get a list of Tenable.sc scan repositories. + description: Requires security manager role. Get a list of Tenable.sc scan repositories. execution: false name: tenable-sc-list-repositories outputs: @@ -500,7 +504,7 @@ script: description: Scan Repository. type: string - deprecated: false - description: Requires admin authentication. Get a list of Tenable.sc scan zones. + description: Requires admin role. Get a list of Tenable.sc scan zones. execution: false name: tenable-sc-list-zones outputs: @@ -712,7 +716,7 @@ script: deprecated: false description: Create a scan on Tenable.sc execution: false - name: Requires security manager authentication. tenable-sc-create-scan + name: Requires security manager role. tenable-sc-create-scan outputs: - contextPath: TenableSC.Scan.ID description: Scan ID. @@ -743,7 +747,7 @@ script: required: true secret: false deprecated: false - description: Requires security manager authentication. Delete a scan in Tenable.sc + description: Requires security manager role. Delete a scan in Tenable.sc execution: false name: tenable-sc-delete-scan - arguments: @@ -759,7 +763,7 @@ script: required: false secret: false deprecated: false - description: Requires security manager authentication. Get a list of Tenable.sc Assets. + description: Requires security manager role. Get a list of Tenable.sc Assets. execution: false name: tenable-sc-list-assets outputs: @@ -819,7 +823,7 @@ script: required: true secret: false deprecated: false - description: Requires security manager authentication. Create an Asset in Tenable.sc with provided IP addresses. + description: Requires security manager role. Create an Asset in Tenable.sc with provided IP addresses. execution: false name: tenable-sc-create-asset outputs: @@ -843,7 +847,7 @@ script: required: true secret: false deprecated: false - description: Requires security manager authentication. Get details for a given asset in Tenable.sc. + description: Requires security manager role. Get details for a given asset in Tenable.sc. execution: false name: tenable-sc-get-asset outputs: @@ -879,7 +883,7 @@ script: required: true secret: false deprecated: false - description: Requires security manager authentication. Delete the Asset with the given ID from Tenable.sc. + description: Requires security manager role. Delete the Asset with the given ID from Tenable.sc. execution: true name: tenable-sc-delete-asset - arguments: @@ -895,7 +899,7 @@ script: required: false secret: false deprecated: false - description: Requires security manager authentication. List alerts from Tenable.sc. + description: Requires security manager role. List alerts from Tenable.sc. execution: false name: tenable-sc-list-alerts outputs: @@ -934,7 +938,7 @@ script: required: true secret: false deprecated: false - description: Requires security manager authentication. Get information about a given alert in Tenable.sc. + description: Requires security manager role. Get information about a given alert in Tenable.sc. execution: false name: tenable-sc-get-alert outputs: @@ -991,7 +995,7 @@ script: required: false secret: false deprecated: false - description: Requires security manager authentication. Gets the specified device information. + description: Requires security manager role. Gets the specified device information. execution: false name: tenable-sc-get-device outputs: @@ -1072,7 +1076,7 @@ script: required: false secret: false deprecated: false - description: Results may vary based on the authentication type (admin or security manager). List users in Tenable.sc. + description: Results may vary based on the role type (admin or security manager). List users in Tenable.sc. execution: false name: tenable-sc-list-users outputs: @@ -1107,7 +1111,7 @@ script: description: User role name. type: string - deprecated: false - description: Requires admin authentication. Retrieve licensing information from Tenable.sc + description: Requires admin role. Retrieve licensing information from Tenable.sc execution: false name: tenable-sc-get-system-licensing outputs: @@ -1121,7 +1125,7 @@ script: description: License status. type: Unknown - deprecated: false - description: Requires admin authentication. Get the system information and diagnostics from Tenable.sc. + description: Requires admin role. Get the system information and diagnostics from Tenable.sc. execution: false name: tenable-sc-get-system-information outputs: @@ -1179,7 +1183,7 @@ script: required: false secret: false deprecated: false - description: Requires security manager authentication. Returns all scan results in Tenable.sc. + description: Requires security manager role. Returns all scan results in Tenable.sc. execution: false name: tenable-sc-get-all-scan-results outputs: @@ -1245,7 +1249,7 @@ script: required: false secret: false deprecated: false - description: Requires security manager authentication. list all groups. + description: Requires security manager role. list all groups. execution: false name: tenable-sc-list-groups outputs: @@ -1434,7 +1438,7 @@ script: required: true secret: false deprecated: false - description: This command can be executed with both authentication types (admin or security manager) based on the roll_id you with to choose. Creates a new user. + description: This command can be executed with both roles (admin or security manager) based on the roll_id you with to choose. Creates a new user. execution: false name: tenable-sc-create-user outputs: @@ -1896,7 +1900,7 @@ script: required: true secret: false deprecated: false - description: This command can be executed with both authentication types (admin or security manager). Delete a user by given user_id. + description: This command can be executed with both roles (admin or security manager). Delete a user by given user_id. execution: false name: tenable-sc-delete-user outputs: @@ -1916,7 +1920,7 @@ script: secret: false - auto: PREDEFINED default: false - description: default is none. none - both active and passive Plugin Families are returned. true - Only active Plugin Families will be returned. false - Only passive Plugin Families will be returned. Ignored when plugin_id is given. + description: Default is none. none - both active and passive Plugin Families are returned. true - Only active Plugin Families will be returned. false - Only passive Plugin Families will be returned. Ignored when plugin_id is given. isArray: false name: is_active predefined: @@ -1925,7 +1929,7 @@ script: required: false secret: false deprecated: false - description: Requires security manager authentication. list plugin families / return information about a plugin family given ID. + description: Requires security manager role. list plugin families / return information about a plugin family given ID. execution: false name: tenable-sc-list-plugin-family outputs: @@ -2029,7 +2033,7 @@ script: - Ignore closed ports(aggressive) - Disabled(softer) deprecated: false - description: Requires security manager authentication. This command is prerequisite for creating remediation scan. creates policy. + description: Requires security manager role. This command is prerequisite for creating remediation scan. creates policy. execution: false name: tenable-sc-create-policy outputs: @@ -2183,7 +2187,7 @@ script: - ticket - user deprecated: false - description: Requires security manager authentication. Lists queries. + description: Requires security manager role. Lists queries. execution: false name: tenable-sc-list-query outputs: @@ -2571,7 +2575,7 @@ script: secret: false deprecated: false polling: true - description: Requires security manager authentication. Update an asset. + description: Requires security manager role. Update an asset. execution: false name: tenable-sc-update-asset outputs: @@ -2794,7 +2798,7 @@ script: secret: false defaultValue: nextDay deprecated: false - description: Requires security manager authentication. This command is prerequisite for creating remediation scan. creates policy. + description: Requires security manager role. This command is prerequisite for creating remediation scan. creates policy. execution: false name: tenable-sc-create-remediation-scan outputs: @@ -3056,7 +3060,7 @@ script: defaultValue: 10800 deprecated: false polling: true - description: Requires security manager authentication. Polling command. Launch a scan by given scan ID, follow its status return a report when the scan is over. + description: Requires security manager role. Polling command. Launch a scan by given scan ID, follow its status return a report when the scan is over. execution: false name: tenable-sc-launch-scan-report outputs: diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_description.md b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_description.md new file mode 100644 index 000000000000..bc84441e0e2b --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_description.md @@ -0,0 +1,31 @@ +Use the Tenable.sc integration to get a real-time, continuous assessment of your security posture so you can find and fix vulnerabilities faster. +All data in Tenable.sc is managed using group level permissions. If you have several groups, data (scans, scan results, assets, etc) can be viewable but not manageable. Users with Security Manager role  can manage everything. These permissions come into play when multiple groups are in use. +It is important to know what data is manageable for the user in order to work with the integration. + +## Use cases: + * Create and run scans. + * Launch and manage scan results and the found vulnerabilities. + * Create and view assets. + * View policies, repositories, credentials, users and more system information. + * View and real-time receiving of alerts. + +**Added support for secret & access keys authentication method.** +- Added support for secret & access keys authentication method (API Key Authentication) which can be generated from the Tenable SC UI while logged into the desired account. +- secret & access keys needs to be generated twice, once for sec man and once for admin. +- First, you need to enable API Key Authentication: + ## [Steps to follow:](https://docs.tenable.com/security-center/Content/EnableAPIKeys.htm) + 1. Log in to Tenable Security Center via the user interface. + 2. Go to System > Configuration. + 3. Click the Security tile. + 4. In the Authentication Settings section, click Allow API Keys to enable the toggle. + 5. Click Submit. +- Second, you need to generate API Key: + ## [Steps to follow:](https://docs.tenable.com/security-center/Content/GenerateAPIKey.htm) + 1. Log in to Tenable Security Center via the user interface. + 2. Go to Users > Users. + 3. Right-click the row for the user for which you want to generate an API key. + -or- + Select the check box for the user for which you want to generate an API key. + 4. Click API Keys > Generate API Key. + 5. Click Generate. + 6. The API Key window appears, displaying the access key and secret key for the user. Save the API keys in a safe location. diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py new file mode 100644 index 000000000000..d87b4597717b --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py @@ -0,0 +1,102 @@ +import pytest +import json +from Tenable_sc import * +import io + +client_mocker = Client(verify_ssl=False, proxy=True, access_key="access_key", secret_key="secret_key", + url="www.tenable_sc_url_mock.com") + + +def load_json(path): + with io.open(path, mode='r', encoding='utf-8') as f: + return json.load(f) + + +@pytest.mark.parametrize("test_case", ["test_case_1"]) +def test_update_asset_command(mocker, test_case): + """ + Given: + - test case that point to the relevant test case in the json test data which include: + args, response mock, and expected hr. + - Case 1: args with asset_id, name, and description to update. + + When: + - Running update_asset_command. + + Then: + - Ensure that the response was parsed correctly and right HR is returned. + - Case 1: Should include the right asset_id in the hr. + """ + test_data = load_json("./test_data/test_update_asset_command.json").get(test_case, {}) + args = test_data.get('args') + mocker.patch.object(client_mocker, 'send_request', return_value=test_data.get('mock_response')) + command_results = update_asset_command(client_mocker, args) + assert test_data.get('expected_hr') == command_results.readable_output + + +# @pytest.mark.parametrize("test_case", ["test_case_1"]) +# def test_get_vulnerability_command(mocker, test_case): +# test_data = load_json("./test_data/test_update_asset_command.json").get(test_case, {}) +# args = test_data.get('args') +# mocker.patch.object(client_mocker, 'send_request', return_value=test_data.get('mock_response')) +# command_results = get_vulnerability_command(client_mocker, args) +# assert test_data.get('expected_hr') == command_results.readable_output + + +@pytest.mark.parametrize("test_case", ["test_case_1", "test_case_2", "test_case_3"]) +def test_list_zones_command(mocker, test_case): + """ + Given: + - test case that point to the relevant test case in the json test data which include: + response mock, expected hr and ec outputs. + - Case 1: A response with empty response field + - Case 2: A response with 2 zones, where the zones also have the same scanners. + - Case 3: A response with 3 zones, where the zones also have different scanners. + + When: + - Running list_zones_command. + + Then: + - Ensure that the response was parsed correctly and right HR and EC outputs are returned. + - Case 1: Should create only scan zones table in HR, and include one zone with ID = 0 and name = all zones in EC. + - Case 2: Should create both tables in HR, the second table should have only one entry. + Should include two zones in the EC and each zone should have a scanner. + - Case 3: Should create both tables in HR, the second table should have two entries. + Should include two zones in the EC and each zone should have a scanner. + """ + test_data = load_json("./test_data/test_list_zones_command.json").get(test_case, {}) + mocker.patch.object(client_mocker, 'send_request', return_value=test_data.get('mock_response')) + command_results = list_zones_command(client_mocker, {}) + assert test_data.get('expected_hr') == command_results.readable_output + assert test_data.get('expected_ec') == command_results.outputs + + +@pytest.mark.parametrize("test_case", ["test_case_1", "test_case_2", "test_case_3"]) +def test_list_groups_command(mocker, test_case): + """ + Given: + - test case that point to the relevant test case in the json test data which include: + args, response mock, expected hr and ec outputs. + - Case 1: Args with limit=1 and show_users=True, and a response with 2 groups where each have users. + - Case 2: Args without limit (default is 50) and show_users=False, and a response with 2 groups where each have users. + - Case 3: Args without limit, and show_users=True, and a response with 2 groups where each have users. + + When: + - Running list_groups_command. + + Then: + - Ensure that the response was parsed correctly and right HR and EC outputs are returned. + - Case 1: Should return information about only 1 group in the HR and EC and include the users table. + - Case 2: Should return information about both groups in the HR and EC, + and exclude the users table and users field from EC. + - Case 3: Should return information about both groups in the HR where each group has its own users table. + And EC where each group include a user field. + """ + test_data = load_json("./test_data/test_list_groups_command.json").get(test_case, {}) + args = test_data.get('args') + mocker.patch.object(client_mocker, 'send_request', return_value=test_data.get('mock_response')) + command_results = list_groups_command(client_mocker, args) + a = test_data.get('expected_hr') + b = command_results.readable_output + assert test_data.get('expected_hr') == command_results.readable_output + assert test_data.get('expected_ec') == command_results.outputs diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_groups_command.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_groups_command.json new file mode 100644 index 000000000000..1940cc6b5e9e --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_groups_command.json @@ -0,0 +1,73 @@ +{ + "test_case_1": { + "args": {"limit": "1", "show_users": "True"}, + "mock_response": { + "error_code":0, "error_msg":"", "response": [{"id":"0", "users": [{"firstname":"test","id":"1","lastname":"","username":"test","uuid":"uuid"}]}, + {"id":"1", "users": [{"firstname":"test","id":"1","lastname":"","username":"test","uuid":"uuid2"}]}], + "timestamp":1684672117,"type":"regular","warnings":[] + }, + "expected_hr": "### Tenable.sc groups\n|ID|\n|---|\n| 0 |\n### Group id:0\n|Username|Firstname|\n|---|---|\n| test | test |\n\n", + "expected_ec": [{ + "Id": "0", + "Users": [ + { + "Firstname": "test", + "Id": "1", + "Lastname": "", + "Username": "test", + "Uuid": "uuid" + } + ] + } + ] + }, + "test_case_2": { + "args": {"show_users": "False"}, + "mock_response": { + "error_code":0, "error_msg":"", "response": [{"id":"0", "users": [{"firstname":"test","id":"1","lastname":"","username":"test","uuid":"uuid"}]}, + {"id":"1", "users": [{"firstname":"test","id":"1","lastname":"","username":"test","uuid":"uuid2"}]}], + "timestamp":1684672117,"type":"regular","warnings":[] + }, + "expected_hr": "### Tenable.sc groups\n|ID|\n|---|\n| 0 |\n| 1 |\n", + "expected_ec": [{ + "Id": "0" + }, + { + "Id": "1" + } + ] + }, + "test_case_3": { + "args": {"show_users": "True"}, + "mock_response": { + "error_code":0, "error_msg":"", "response": [{"id":"0", "users": [{"firstname":"test","id":"1","lastname":"","username":"test","uuid":"uuid"}]}, + {"id":"1", "users": [{"firstname":"test","id":"1","lastname":"","username":"test","uuid":"uuid2"}]}], + "timestamp":1684672117,"type":"regular","warnings":[] + }, + "expected_hr": "### Tenable.sc groups\n|ID|\n|---|\n| 0 |\n| 1 |\n### Group id:0\n|Username|Firstname|\n|---|---|\n| test | test |\n\n### Group id:1\n|Username|Firstname|\n|---|---|\n| test | test |\n\n", + "expected_ec": [{ + "Id": "0", + "Users": [ + { + "Firstname": "test", + "Id": "1", + "Lastname": "", + "Username": "test", + "Uuid": "uuid" + } + ] + },{ + "Id": "1", + "Users": [ + { + "Firstname": "test", + "Id": "1", + "Lastname": "", + "Username": "test", + "Uuid": "uuid2" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_zones_command.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_zones_command.json new file mode 100644 index 000000000000..c8cbf58b8d25 --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_zones_command.json @@ -0,0 +1,91 @@ +{ + "test_case_1": { + "mock_response": { + "error_code": 0, "error_msg": "", "response": [], "timestamp": 1684668050, "type": "regular", "warnings": [] + }, + "expected_hr": "### Tenable.sc Scan Zones\n|ID|Name|\n|---|---|\n| 0 | All Zones |\n", + "expected_ec": [{ + "ID": 0, "Name": "All Zones" + }] + }, + "test_case_2": { + "mock_response": {"error_code":0,"error_msg":"","response":[{"SCI":{"description":"This Tenable.sc system","id":"1","name":"Local"},"activeScanners":1, + "createdTime":"1673270031","description":"","id":"1","ipList":"ip","modifiedTime":"1676369613","name":"Default Scan Zone","organizations":[], + "scanners":[{"description":"","id":"2","name":"RHEL6 Scanner","status":"1"}],"totalScanners":1,"uuid":"uuid"}, + {"SCI":{"description":"This Tenable.sc system","id":"1","name":"Local"},"activeScanners":1,"createdTime":"1676464328","description":"","id":"3","ipList":"ip2", + "modifiedTime":"1676464328","name":"Windows","organizations":[],"scanners":[{"description":"","id":"2","name":"RHEL6 Scanner","status":"1"}],"totalScanners":1, + "uuid":"uuid2"}],"timestamp":1684668642,"type":"regular","warnings":[]}, + "expected_hr": "### Tenable.sc Scan Zones\n|ID|Name|IPList|activeScanners|\n|---|---|---|---|\n| 1 | Default Scan Zone | ip | 1 |\n| 3 | Windows | ip2 | 1 |\n### Tenable.sc Scanners\n|ID|Name|Status|\n|---|---|---|\n| 2 | RHEL6 Scanner | 1 |\n", + "expected_ec": [ + { + "ID": "1", + "IPList": "ip", + "Name": "Default Scan Zone", + "Scanner": [ + { + "Description": "", + "ID": "2", + "Name": "RHEL6 Scanner", + "Status": "1" + } + ], + "activeScanners": 1 + }, + { + "ID": "3", + "IPList": "ip2", + "Name": "Windows", + "Scanner": [ + { + "Description": "", + "ID": "2", + "Name": "RHEL6 Scanner", + "Status": "1" + } + ], + "activeScanners": 1 + } + ] + + }, + "test_case_3": { + "mock_response": {"error_code":0,"error_msg":"","response":[{"SCI":{"description":"This Tenable.sc system","id":"1","name":"Local"},"activeScanners":1, + "createdTime":"1673270031","description":"","id":"1","ipList":"ip","modifiedTime":"1676369613","name":"Default Scan Zone","organizations":[], + "scanners":[{"description":"","id":"1","name":"RHEL6 Scanner","status":"1"}],"totalScanners":1,"uuid":"uuid"}, + {"SCI":{"description":"This Tenable.sc system","id":"1","name":"Local"},"activeScanners":1,"createdTime":"1676464328","description":"","id":"3","ipList":"ip2", + "modifiedTime":"1676464328","name":"Windows","organizations":[],"scanners":[{"description":"","id":"2","name":"RHEL6 Scanner","status":"1"}],"totalScanners":1, + "uuid":"uuid2"}],"timestamp":1684668642,"type":"regular","warnings":[]}, + "expected_hr": "### Tenable.sc Scan Zones\n|ID|Name|IPList|activeScanners|\n|---|---|---|---|\n| 1 | Default Scan Zone | ip | 1 |\n| 3 | Windows | ip2 | 1 |\n### Tenable.sc Scanners\n|ID|Name|Status|\n|---|---|---|\n| 1 | RHEL6 Scanner | 1 |\n| 2 | RHEL6 Scanner | 1 |\n", + "expected_ec": [ + { + "ID": "1", + "IPList": "ip", + "Name": "Default Scan Zone", + "Scanner": [ + { + "Description": "", + "ID": "1", + "Name": "RHEL6 Scanner", + "Status": "1" + } + ], + "activeScanners": 1 + }, + { + "ID": "3", + "IPList": "ip2", + "Name": "Windows", + "Scanner": [ + { + "Description": "", + "ID": "2", + "Name": "RHEL6 Scanner", + "Status": "1" + } + ], + "activeScanners": 1 + } + ] + + } +} \ No newline at end of file diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_update_asset_command.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_update_asset_command.json new file mode 100644 index 000000000000..10cd1132c795 --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_update_asset_command.json @@ -0,0 +1,51 @@ +{ + "test_case_1": { + "args": { + "asset_id": "1", + "name": "asset_1_name", + "description": "asset_1_description" + }, + "mock_response": {"error_code":0, "error_msg":"", "response": { + "assetDataFields":[], "canManage":"true", "canUse":"true", "context":"", "createdTime":"1673270083", "creator": { + "firstname":"test", "id":"1", "lastname":"", "username":"test", "uuid":"test_uuid" + }, + "description": "asset_1_description", "id":"1", "ioFirstSyncTime":"-1", "ioLastSyncFailure":"-1", "ioLastSyncSuccess":"-1", "ioSyncErrorDetails":null, "ioSyncStatus":"Not Synced", "ipCount":-1, + "modifiedTime":"1684660372", "name":"asset_1_name", "owner": { + "firstname": "test", "id": "1", "lastname": "", "username": "test", "uuid":"test_uuid" + }, + "ownerGroup": { + "description": "Full Access group", "id": "0", "name":"Full Access" + }, + "repositories": [ + { + "ipCount": "-1", "repository": { + "description": "", "id":"1", "name":"Local", "type":"Local", "uuid":"test_uuid" + } + } + ], + "status":"0", "tags":"", "targetGroup": { + "description":"", "id":-1, "name": "" + }, + "template": { + "description": "This asset uses passive plugins for OS detection and hop detection to identify hosts on the network.", "id": "451", "name": "Systems Discovered Passively" + }, + "type": "dynamic", "typeFields": { + "rules": { + "children": [ + { + "filterName": "pluginid", "operator": "eq", "pluginIDConstraint": "-1", "type": "clause", "value":"12" + }, + { + "filterName": "pluginid", "operator": "eq", "pluginIDConstraint": "-1", "type": "clause", "value": "1" + } + ], + "operator": "any", "type": "group" + } + }, "uuid": "test_uuid" + }, + "timestamp": 1684660372, + "type": "regular", "warnings":[] + }, + "expected_hr": "asset 1 was updated successfully." + } +} \ No newline at end of file From b42f020ebeac971cfc728fae4fb58606b9ecd019 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Sun, 21 May 2023 19:23:57 +0300 Subject: [PATCH 031/115] fixes --- .../Integrations/Tenable_sc/Tenable_sc.py | 152 +++++++++++------- .../Tenable_sc/Tenable_sc_test.py | 31 +++- 2 files changed, 121 insertions(+), 62 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index eaeea8851dac..9ddaab7fede5 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -565,24 +565,74 @@ def delete_query(self, query_id): path = 'query/' + str(query_id) self.send_request(path, method='delete') - def get_analysis(self, query, scan_results_id): - path = 'analysis' + def get_analysis(self, query=None, scan_results_id=None, args={}): + """ + Send the request for get_vulnerability_command and get_vulnerabilities. + Args: + query (dict or str): This function can receive query argument either as a dict (as in get_vulnerability_command), + or as an ID of an existing query (as in get_vulnerabilities). + scan_results_id (None or str): str if received from get_vulnerabilities, otherwise will be part of args. + args (dict): Either an empty dict if passed from get_vulnerabilities, otherwise, the demisto.results() object. + Returns: + Dict: The response. + """ + body = self.create_get_vulnerability_request_body(args, query, scan_results_id) - # This function can receive 'query' argument either as a dict (as in get_vulnerability_command), - # or as an ID of an existing query (as in get_vulnerabilities). - # Here we form the query field in the request body as a dict, as required. - if not isinstance(query, dict): - query = {'id': query} + return self.send_request(path='analysis', method='post', body=body) + def create_get_vulnerability_request_body(self, args={}, query=None, scan_results_id=None): + """ + Create the body for the request made in get_analysis. + Args: + query (dict or str): This function can receive query argument either as a dict (as in get_vulnerability_command), + or as an ID of an existing query (as in get_vulnerabilities). + scan_results_id (None or str): str if received from get_vulnerabilities, otherwise will be part of args. + args (dict): Either an empty dict if passed from get_vulnerabilities, otherwise, the demisto.results() object. + Returns: + Dict: The prepared request body. + """ + vuln_id = args.get('vulnerability_id') + scan_results_id = scan_results_id or args.get('scan_results_id') + sort_field = args.get('sort_field') + query_id = query or args.get('query_id') + if not isinstance(query_id, dict): + query = {'id': query_id} + sort_direction = args.get('sort_direction') + source_type = args.get('source_type', "individual") + page = int(args.get('page', '0')) + limit = int(args.get('limit', '50')) + if limit > 200: + limit = 200 body = { + 'tool': 'vulndetails', 'type': 'vuln', - 'query': query, - 'sourceType': 'individual', - 'scanID': scan_results_id, - 'view': 'all' + 'startOffset': page, # Lower bound for the results list (must be specified) + 'endOffset': page + limit, # Upper bound for the results list (must be specified) + 'sortField': sort_field, + 'sortDir': sort_direction, + 'sourceType': source_type, + 'view': 'all', } + if source_type == 'individual': + if scan_results_id: + body['scanID'] = scan_results_id + vuln_filter = [{ + 'filterName': 'pluginID', + 'operator': '=', + 'value': vuln_id + }] + query["filters"] = vuln_filter + query["tool"] = 'vulndetails' + query["type"] = 'vuln' + else: + return_error("When choosing source_type = individual - scan_results_id must be provided.") + else: + body['sourceType'] = source_type + if not query_id: + return_error(f"When choosing source_type = {source_type} - query_id must be provided.") + body["query"] = query - return self.send_request(path, method='post', body=body) + return body def get_system_diagnostics(self): """ @@ -667,7 +717,8 @@ def get_device(self, uuid, ip, dns_name, repo): return self.send_request(path, params=params) - def get_users(self, fields, user_id): + def get_users(self, fields='id,username,firstname,lastname,title,email,createdTime,modifiedTime,lastLogin,role', + user_id=None): """ Send the request for list_users_command. Args: @@ -1673,7 +1724,7 @@ def get_vulnerabilities(client: Client, scan_results_id): if not query or 'response' not in query: return 'Could not get vulnerabilites query' - analysis = client.get_analysis(query['response']['id'], scan_results_id) + analysis = client.get_analysis(query=query['response']['id'], scan_results_id=scan_results_id) client.delete_query(query.get('response', {}).get('id')) @@ -1723,42 +1774,8 @@ def get_vulnerability_command(client: Client, args: Dict[str, Any]): """ vuln_id = args.get('vulnerability_id') scan_results_id = args.get('scan_results_id') - sort_field = args.get('sort_field') - query_id = args.get('query_id') - sort_direction = args.get('sort_direction') - source_type = args.get('source_type', "individual") - page = int(args.get('page', '0')) - limit = int(args.get('limit', '50')) - if limit > 200: - limit = 200 - query = { - 'tool': 'vulndetails', - 'type': 'vuln', - 'startOffset': page, # Lower bound for the results list (must be specified) - 'endOffset': page + limit, # Upper bound for the results list (must be specified) - 'sortField': sort_field, - 'sortDir': sort_direction - } - - if source_type == 'individual': - if scan_results_id: - query['scanID'] = scan_results_id - vuln_filter = [{ - 'filterName': 'pluginID', - 'operator': '=', - 'value': vuln_id - }] - query["filters"] = vuln_filter - else: - return_error("When choosing source_type = individual - scan_results_id must be provided.") - else: - if query_id: - query["id"] = query_id - else: - return_error(f"When choosing source_type = {source_type} - query_id must be provided.") - - analysis = client.get_analysis(query, scan_results_id) + analysis = client.get_analysis(args=args) if not analysis or 'response' not in analysis: return_error('Error: Could not get vulnerability analysis') @@ -1868,10 +1885,10 @@ def get_vulnerability_hosts_from_analysis(results): List: list of all the vulnerability hosts extracted from the results. """ return [{ - 'IP': host['ip'], - 'MAC': host['macAddress'], - 'Port': host['port'], - 'Protocol': host['protocol'] + 'IP': host.get('ip'), + 'MAC': host.get('macAddress'), + 'Port': host.get('port'), + 'Protocol': host.get('protocol') } for host in results] @@ -2497,7 +2514,8 @@ def list_plugin_family_command(client: Client, args: Dict[str, Any]): return_error('No plugins found') plugins = res.get('response') if isinstance(plugins, dict): - is_active = "false" if plugins.get("type") == "passive" else "true" + if plugin_type := plugins.get("type") in ["active", "passive"]: + is_active = "false" if plugin_type == "passive" else "true" plugins = [plugins] if len(plugins) > limit: plugins = plugins[:limit] @@ -2506,7 +2524,6 @@ def list_plugin_family_command(client: Client, args: Dict[str, Any]): for mapped_plugin in mapped_plugins: mapped_plugin["Is Active"] = is_active headers = ["Plugin ID", "Plugin Name", "Is Active"] - return CommandResults( outputs=createContext(response_to_context(plugins), removeNull=True), outputs_prefix='TenableSC.PluginFamily', @@ -2730,6 +2747,24 @@ def list_queries(client: Client, type): return res, hr, queries +def test_module(client: Client, args: Dict[str, Any]): + """ + Lists queries and return the processed results. + Args: + client (Client): The tenable.sc client object. + type (str): query time to filter by. + Returns: + Dict: The response from the server. + str: The processed human readable. + Dict: The relevant section from the response. + """ + try: + client.get_users() + return "ok" + except Exception: + raise Exception("Authorization Error: make sure your API Key and Secret Key are correctly set") + + def main(): params = demisto.params() command = demisto.command() @@ -2745,6 +2780,7 @@ def main(): demisto.info(f'Executing command {command}') command_dict = { + 'test-module': test_module, 'tenable-sc-list-scans': list_scans_command, 'tenable-sc-list-policies': list_policies_command, 'tenable-sc-list-repositories': list_repositories_command, @@ -2789,11 +2825,7 @@ def main(): secret_key=secret_key, url=url ) - - if command == 'test-module': - list_users_command(client, args) - demisto.results('ok') - elif command == 'fetch-incidents': + if command == 'fetch-incidents': first_fetch = params.get('fetch_time').strip() fetch_incidents(client, first_fetch) elif command == 'tenable-sc-launch-scan-report': diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py index d87b4597717b..342ed8764fd9 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py @@ -96,7 +96,34 @@ def test_list_groups_command(mocker, test_case): args = test_data.get('args') mocker.patch.object(client_mocker, 'send_request', return_value=test_data.get('mock_response')) command_results = list_groups_command(client_mocker, args) - a = test_data.get('expected_hr') - b = command_results.readable_output + assert test_data.get('expected_hr') == command_results.readable_output + assert test_data.get('expected_ec') == command_results.outputs + + +@pytest.mark.parametrize("test_case", ["test_case_1", "test_case_2", "test_case_3", "test_case_4"]) +def test_list_plugin_family_command(mocker, test_case): + """ + Given: + - test case that point to the relevant test case in the json test data which include: + args, response mock, expected hr and ec outputs. + - Case 1: Args with limit=1 and is_active=true, and a response with 2 plugin families. + - Case 2: Empty args, and a response with 2 plugins. + - Case 3: Args with plugin_id, and a response with the plugin family information where the plugin type is malware. + - Case 4: Args with plugin_id, and a response with the plugin family information where the plugin type is active. + + When: + - Running list_plugin_family_command. + + Then: + - Ensure that the response was parsed correctly and right HR and EC outputs are returned. + - Case 1: Should return a table with is_active column in the HR, and EC that include only the first plugin family. + - Case 2: Should return a table with name column in the HR, and EC that include both plugin families. + - Case 3: Should return a table with only id and name columns, and EC with plugin type in it. + - Case 4: Should return a table with all columns (id, name, and is_active), and EC with plugin type in it. + """ + test_data = load_json("./test_data/test_list_plugin_family_command.json").get(test_case, {}) + args = test_data.get('args') + mocker.patch.object(client_mocker, 'send_request', return_value=test_data.get('mock_response')) + command_results = list_plugin_family_command(client_mocker, args) assert test_data.get('expected_hr') == command_results.readable_output assert test_data.get('expected_ec') == command_results.outputs From b346507416d2d2a27b049d2c149422e835a09d64 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Mon, 22 May 2023 10:54:02 +0300 Subject: [PATCH 032/115] tests --- .../Integrations/Tenable_sc/Tenable_sc.py | 130 +++++++++--------- .../Tenable_sc/Tenable_sc_test.py | 35 +++++ .../test_list_plugin_family_command.json | 54 ++++++++ .../test_validate_user_body_params.json | 32 +++++ 4 files changed, 186 insertions(+), 65 deletions(-) create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_plugin_family_command.json create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_user_body_params.json diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 9ddaab7fede5..2ec5be770b7a 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -43,7 +43,7 @@ def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str 'Content-Type': 'application/json' } if not (user_name and password) and not (secret_key and access_key): - return_error("Please provide either user_name and password or secret_key and access_key") + raise DemistoException("Please provide either user_name and password or secret_key and access_key") if secret_key and access_key: self.headers['x-apikey'] = f"accesskey={access_key}; secretkey={secret_key}" BaseClient.__init__(self, base_url=self.url, headers=self.headers, verify=verify_ssl, proxy=proxy) @@ -109,11 +109,11 @@ def send_request_old(self, path, method='get', body=None, params=None, headers=N error = res.json() except Exception: # type: ignore - return_error( + raise DemistoException( f'Error: Got status code {str(res.status_code)} with {url=} \ with body {res.content} with headers {str(res.headers)}') # type: ignore - return_error(f"Error: Got an error from TenableSC, code: {error['error_code']}, \ + raise DemistoException(f"Error: Got an error from TenableSC, code: {error['error_code']}, \ details: {error['error_msg']}") # type: ignore return res.json() @@ -128,7 +128,7 @@ def login(self): login_response = self.send_login_request(login_body) if 'response' not in login_response: - return_error('Error: Could not retrieve login token') + raise DemistoException('Error: Could not retrieve login token') token = login_response['response'].get('token') # There might be a case where the API does not return a token because there are too many sessions with the same user @@ -137,7 +137,7 @@ def login(self): login_body['releaseSession'] = 'true' login_response = self.send_login_request(login_body) if 'response' not in login_response or 'token' not in login_response['response']: - return_error('Error: Could not retrieve login token') + raise DemistoException('Error: Could not retrieve login token') token = login_response['response']['token'] self.token = str(token) @@ -160,7 +160,7 @@ def send_login_request(self, login_body): res = self.session.request('post', url, headers=headers, data=json.dumps(login_body), verify=self.verify_ssl) if res.status_code < 200 or res.status_code >= 300: - return_error(f'Error: Got status code {str(res.status_code)} with {url=} \ + raise DemistoException(f'Error: Got status code {str(res.status_code)} with {url=} \ with body {res.content} with headers {str(res.headers)}') # type: ignore self.cookie = res.cookies.get('TNS_SESSIONID', self.cookie) @@ -248,15 +248,15 @@ def create_scan(self, args): if time_zone := args.get("time_zone") and start_time: body['schedule']['start'] = f"TZID={time_zone}:{start_time}" else: - return_error("Please make sure to provide both time_zone and start_time.") + raise DemistoException("Please make sure to provide both time_zone and start_time.") if all([repeat_rule_freq, repeat_rule_interval, repeat_rule_by_day]): body['schedule']['repeatRule'] = f"FREQ={repeat_rule_freq};INTERVAL={repeat_rule_interval};" f"BYDAY={repeat_rule_by_day}" elif repeat_rule_freq and repeat_rule_interval: body['schedule']['repeatRule'] = f"FREQ={repeat_rule_freq};INTERVAL={repeat_rule_interval}" elif any([repeat_rule_freq, repeat_rule_interval, repeat_rule_by_day]): - return_error("Please make sure to provide repeat_rule_freq, repeat_rule_interval with or without " - "repeat_rule_by_day, or don't provide any of them.") + raise DemistoException("Please make sure to provide repeat_rule_freq, repeat_rule_interval with or without " + "repeat_rule_by_day, or don't provide any of them.") body['schedule']['enabled'] = argToBoolean(args.get("enabled", True)) return self.send_request(path='scan', method='post', body=body) @@ -561,7 +561,7 @@ def delete_query(self, query_id): Dict: The response. """ if not query_id: - return_error('query id returned None') + raise DemistoException('query id returned None') path = 'query/' + str(query_id) self.send_request(path, method='delete') @@ -625,11 +625,11 @@ def create_get_vulnerability_request_body(self, args={}, query=None, scan_result query["tool"] = 'vulndetails' query["type"] = 'vuln' else: - return_error("When choosing source_type = individual - scan_results_id must be provided.") + raise DemistoException("When choosing source_type = individual - scan_results_id must be provided.") else: body['sourceType'] = source_type if not query_id: - return_error(f"When choosing source_type = {source_type} - query_id must be provided.") + raise DemistoException(f"When choosing source_type = {source_type} - query_id must be provided.") body["query"] = query return body @@ -932,26 +932,26 @@ def validate_user_body_params(args, command_type): try: int(args.get(number_arg, '0')) except Exception: - return_error(f"{number_arg} must be a valid number.") + raise DemistoException(f"{number_arg} must be a valid number.") if time_zone and time_zone not in pytz.all_timezones: - return_error("Invalid time zone ID. Please choose one of the following: " + raise DemistoException("Invalid time zone ID. Please choose one of the following: " "https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html") - if command_type == "create" and (auth_type == 'Ldap' or auth_type == 'saml') and args.get("must_change_password"): - return_error(f"When choosing {auth_type=}, must_change_password must be set to False.") + if command_type == "create" and (auth_type == 'Ldap' or auth_type == 'saml'): + args["must_change_password"] = "false" if password: if command_type == 'update' and not args.get("current_password"): - return_error("current_password must be provided when attempting to update password.") + raise DemistoException("current_password must be provided when attempting to update password.") if len(password) < 3: - return_error("Password length must be at least 3 characters.") + raise DemistoException("Password length must be at least 3 characters.") if email and not re.compile(emailRegex).match(email): - return_error(f"Error: The given email address: {email} is not valid") + raise DemistoException(f"Error: The given email address: {email} is not valid") if command_type == 'create' and not email_notice == 'none' and not email: - return_error("When email_notice is different from none, an email must be given as well.") + raise DemistoException("When email_notice is different from none, an email must be given as well.") def timestamp_to_utc(timestamp_str, default_returned_value=''): @@ -999,12 +999,12 @@ def list_scans_command(client: Client, args: Dict[str, Any]): manageable = args.get('manageable', 'false').lower() if not res or 'response' not in res or not res['response']: - return_error('No scans found') + raise DemistoException('No scans found') scans_dicts = get_elements(res['response'], manageable) if len(scans_dicts) == 0: - return_error('No scans found') + raise DemistoException('No scans found') headers = ['ID', 'Name', 'Description', 'Policy', 'Group', 'Owner'] @@ -1040,12 +1040,12 @@ def list_policies_command(client: Client, args: Dict[str, Any]): manageable = args.get('manageable', 'false').lower() if not res or 'response' not in res or not res['response']: - return_error('No policies found') + raise DemistoException('No policies found') policies = get_elements(res['response'], manageable) if len(policies) == 0: - return_error('No policies found') + raise DemistoException('No policies found') headers = ['ID', 'Name', 'Description', 'Tag', 'Type', 'Group', 'Owner', 'LastModified'] @@ -1081,12 +1081,12 @@ def list_repositories_command(client: Client, args: Dict[str, Any]): res = client.get_repositories() if not res or 'response' not in res or not res['response']: - return_error('No repositories found') + raise DemistoException('No repositories found') repositories = res['response'] if len(repositories) == 0: - return_error('No repositories found') + raise DemistoException('No repositories found') headers = [ 'ID', @@ -1119,12 +1119,12 @@ def list_credentials_command(client: Client, args: Dict[str, Any]): manageable = args.get('manageable', 'false').lower() if not res or 'response' not in res or not res['response']: - return_error('No credentials found') + raise DemistoException('No credentials found') credentials = get_elements(res['response'], manageable) if len(credentials) == 0: - return_error('No credentials found') + raise DemistoException('No credentials found') headers = ['ID', 'Name', 'Description', 'Type', 'Tag', 'Group', 'Owner', 'LastModified'] @@ -1162,12 +1162,12 @@ def list_assets_command(client: Client, args: Dict[str, Any]): manageable = args.get('manageable', 'false').lower() if not res or 'response' not in res or not res['response']: - return_error('No assets found') + raise DemistoException('No assets found') assets = get_elements(res['response'], manageable) if len(assets) == 0: - return_error('No assets found') + raise DemistoException('No assets found') headers = ['ID', 'Name', 'Tag', 'Owner', 'Group', 'Type', 'HostCount', 'LastModified'] @@ -1205,7 +1205,7 @@ def get_asset_command(client: Client, args: Dict[str, Any]): res = client.get_asset(asset_id) if not res or 'response' not in res: - return_error('Asset not found') + raise DemistoException('Asset not found') asset = res['response'] @@ -1257,7 +1257,7 @@ def create_asset_command(client: Client, args: Dict[str, Any]): res = client.create_asset(name, description, owner_id, tags, ips) if not res or 'response' not in res: - return_error('Error: Could not retrieve the asset') + raise DemistoException('Error: Could not retrieve the asset') asset = res['response'] @@ -1298,7 +1298,7 @@ def delete_asset_command(client: Client, args: Dict[str, Any]): res = client.delete_asset(asset_id) if not res: - return_error('Error: Could not delete the asset') + raise DemistoException('Error: Could not delete the asset') return CommandResults( raw_response=res, @@ -1320,7 +1320,7 @@ def list_report_definitions_command(client: Client, args: Dict[str, Any]): manageable = args.get('manageable', 'false').lower() if not res or 'response' not in res or not res['response']: - return_error('No report definitions found') + raise DemistoException('No report definitions found') reports = get_elements(res['response'], manageable) # Remove duplicates, take latest @@ -1328,7 +1328,7 @@ def list_report_definitions_command(client: Client, args: Dict[str, Any]): filter(lambda e: e['name'] == n, reports)) for n in {r['name'] for r in reports}] if len(reports) == 0: - return_error('No report definitions found') + raise DemistoException('No report definitions found') headers = ['ID', 'Name', 'Description', 'Type', 'Group', 'Owner'] @@ -1365,7 +1365,7 @@ def list_zones_command(client: Client, args: Dict[str, Any]): """ res = client.get_zones() if not res or 'response' not in res: - return_error('No zones found') + raise DemistoException('No zones found') zones = res['response'] if len(zones) == 0: zones = [{ @@ -1444,7 +1444,7 @@ def create_scan_command(client: Client, args: Dict[str, Any]): res = client.create_scan(args) if not res or 'response' not in res: - return_error('Error: Could not retrieve the scan') + raise DemistoException('Error: Could not retrieve the scan') scan = res['response'] @@ -1492,13 +1492,13 @@ def validate_create_scan_inputs(args): time_zone = args.get("time_zone") if time_zone and time_zone not in pytz.all_timezones: - return_error("Invalid time zone ID. Please choose one of the following: " + raise DemistoException("Invalid time zone ID. Please choose one of the following: " "https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html") if not asset_ids and not ips: - return_error('Error: Assets and/or IPs must be provided') + raise DemistoException('Error: Assets and/or IPs must be provided') if schedule == 'dependent' and not dependent: - return_error('Error: Dependent schedule must include a dependent scan ID') + raise DemistoException('Error: Dependent schedule must include a dependent scan ID') def launch_scan_command(client: Client, args: Dict[str, Any]): @@ -1583,12 +1583,12 @@ def launch_scan(client: Client, args: Dict[str, Any]): target_password = args.get('diagnostic_password') if (target_address and not target_password) or (target_password and not target_address): - return_error('Error: If a target is provided, both IP/Hostname and the password must be provided') + raise DemistoException('Error: If a target is provided, both IP/Hostname and the password must be provided') res = client.launch_scan(scan_id, {'address': target_address, 'password': target_password}) if not res or 'response' not in res or not res['response'] or 'scanResult' not in res['response']: - return_error('Error: Could not retrieve the scan') + raise DemistoException('Error: Could not retrieve the scan') return res @@ -1638,7 +1638,7 @@ def get_scan_status(client: Client, args: Dict[str, Any]): for scan_results_id in scan_results_ids: res = client.get_scan_results(scan_results_id) if not res or 'response' not in res or not res['response']: - return_error('Scan results not found') + raise DemistoException('Scan results not found') scans_results.append(res['response']) return scans_results, res @@ -1659,7 +1659,7 @@ def get_scan_report_command(client: Client, args: Dict[str, Any]): res = client.get_scan_report(scan_results_id) if not res or 'response' not in res or not res['response']: - return_error('Scan results not found') + raise DemistoException('Scan results not found') scan_results = res['response'] @@ -1778,17 +1778,17 @@ def get_vulnerability_command(client: Client, args: Dict[str, Any]): analysis = client.get_analysis(args=args) if not analysis or 'response' not in analysis: - return_error('Error: Could not get vulnerability analysis') + raise DemistoException('Error: Could not get vulnerability analysis') results = analysis['response']['results'] if not results or len(results) == 0: - return_error('Error: Vulnerability not found in the scan results') + raise DemistoException('Error: Vulnerability not found in the scan results') vuln_response = client.get_vulnerability(vuln_id) if not vuln_response or 'response' not in vuln_response: - return_error('Vulnerability not found') + raise DemistoException('Vulnerability not found') vuln = vuln_response['response'] vuln['severity'] = results[0]['severity'] # The vulnerability severity is the same in all the results @@ -1906,7 +1906,7 @@ def delete_scan_command(client: Client, args: Dict[str, Any]): res = client.delete_scan(scan_id) if not res: - return_error('Error: Could not delete the scan') + raise DemistoException('Error: Could not delete the scan') return CommandResults( raw_response=res, @@ -1931,7 +1931,7 @@ def get_device_command(client: Client, args: Dict[str, Any]): res = client.get_device(uuid, ip, dns_name, repo) if not res or 'response' not in res: - return_error('Device not found') + raise DemistoException('Device not found') device = res['response'] @@ -2012,7 +2012,7 @@ def list_users_command(client: Client, args: Dict[str, Any]): res = client.get_users('id,username,firstname,lastname,title,email,createdTime,modifiedTime,lastLogin,role', user_id) if not res or 'response' not in res: - return_error('No users found') + raise DemistoException('No users found') users = res['response'] @@ -2026,7 +2026,7 @@ def list_users_command(client: Client, args: Dict[str, Any]): users = list(filter(lambda u: u['email'] == email, users)) if len(users) == 0: - return_error('No users found') + raise DemistoException('No users found') headers = [ 'ID', @@ -2075,7 +2075,7 @@ def get_system_licensing_command(client: Client, args: Dict[str, Any]): res = client.get_system_licensing() if not res or 'response' not in res: - return_error('Error: Could not retrieve system licensing') + raise DemistoException('Error: Could not retrieve system licensing') status = res['response'] @@ -2112,12 +2112,12 @@ def get_system_information_command(client: Client, args: Dict[str, Any]): sys_res = client.get_system() if not sys_res or 'response' not in sys_res: - return_error('Error: Could not retrieve system information') + raise DemistoException('Error: Could not retrieve system information') diag_res = client.get_system_diagnostics() if not diag_res or 'response' not in diag_res: - return_error('Error: Could not retrieve system information') + raise DemistoException('Error: Could not retrieve system information') sys_res.update(diag_res) diagnostics = diag_res['response'] @@ -2170,12 +2170,12 @@ def list_alerts_command(client: Client, args: Dict[str, Any]): manageable = args.get('manageable', 'false').lower() if not res or 'response' not in res or not res['response']: - return_error('No alerts found') + raise DemistoException('No alerts found') alerts = get_elements(res['response'], manageable) if len(alerts) == 0: - return_error('No alerts found') + raise DemistoException('No alerts found') headers = ['ID', 'Name', 'Actions', 'State', 'LastTriggered', 'LastEvaluated', 'Group', 'Owner'] mapped_alerts = [{ @@ -2211,7 +2211,7 @@ def get_alert_command(client: Client, args: Dict[str, Any]): res = client.get_alerts(alert_id=alert_id) if not res or 'response' not in res or not res['response']: - return_error('Alert not found') + raise DemistoException('Alert not found') alert = res['response'] query_res = client.get_query(alert['query'].get('id')) @@ -2322,7 +2322,7 @@ def list_groups_command(client: Client, args: Dict[str, Any]): limit = int(args.get('limit', '50')) res = client.list_groups(show_users) if not res or not res.get('response', []): - return_error('No groups found') + raise DemistoException('No groups found') groups = res.get('response', []) if len(groups) > limit: groups = groups[:limit] @@ -2375,7 +2375,7 @@ def get_all_scan_results_command(client: Client, args: Dict[str, Any]): limit = 200 if not res or 'response' not in res or not res['response']: - return_error('Scan results not found') + raise DemistoException('Scan results not found') elements = get_elements(res['response'], get_manageable_results) @@ -2453,7 +2453,7 @@ def process_update_and_create_user_response(res, hr_header): CommandResults: command results object with the response, human readable section, and the context entries to add. """ if not res or not res.get('response', {}): - return_error("User wasn't created successfully.") + raise DemistoException("User wasn't created successfully.") headers = ["User type", "User Id", "User Status", "User Name", "First Name", "Lat Name ", "Email ", "User Role Name", "User Group Name", "User LDAP Name"] response = res.get("response", {}) @@ -2511,7 +2511,7 @@ def list_plugin_family_command(client: Client, args: Dict[str, Any]): plugin_id = args.get('plugin_id', '') res = client.list_plugin_family(plugin_id, is_active) if not res or not res.get('response', []): - return_error('No plugins found') + raise DemistoException('No plugins found') plugins = res.get('response') if isinstance(plugins, dict): if plugin_type := plugins.get("type") in ["active", "passive"]: @@ -2590,7 +2590,7 @@ def create_remediation_scan_command(client: Client, args: Dict[str, Any]): args["policy_id"] = created_policy.get("id") res = client.create_scan(args) if not res or 'response' not in res: - return_error('Error: Could not retrieve the scan') + raise DemistoException('Error: Could not retrieve the scan') scan = res.get('response', {}) @@ -2662,7 +2662,7 @@ def update_asset_command(client: Client, args: Dict[str, Any]): asset_id = args.get('asset_id') res = client.update_asset(args, asset_id) if not res or not res.get('response', []): - return_error("Couldn't update asset.") + raise DemistoException("Couldn't update asset.") return CommandResults( raw_response=res, @@ -2683,7 +2683,7 @@ def get_query(client: Client, query_id): """ res = client.get_query(query_id) if not res or not res.get('response', []): - return_error(f"The query {query_id} wasn't found") + raise DemistoException(f"The query {query_id} wasn't found") query = res.get('response') mapped_query = { "Query Id": query_id, @@ -2709,7 +2709,7 @@ def list_queries(client: Client, type): """ res = client.list_queries(type) if not res or not res.get('response', []): - return_error("No queries found.") + raise DemistoException("No queries found.") queries = res.get('response') manageable_queries = queries.get("manageable", []) usable_queries = queries.get("usable", []) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py index 342ed8764fd9..a5fd26c26088 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py @@ -127,3 +127,38 @@ def test_list_plugin_family_command(mocker, test_case): command_results = list_plugin_family_command(client_mocker, args) assert test_data.get('expected_hr') == command_results.readable_output assert test_data.get('expected_ec') == command_results.outputs + + +@pytest.mark.parametrize("test_case", ["test_case_1", "test_case_2", "test_case_3", "test_case_4", "test_case_5", "test_case_6"]) +def test_validate_user_body_params(test_case): + """ + Given: + - test case that point to the relevant test case in the json test data which include: + args, command_type flag and the expected error message. + - Case 1: Args with group_id which is not a number, and a command_type flag that point to create. + - Case 2: Args with invalid time_zone, and a command_type flag that point to create. + - Case 3: Args with only password, and a command_type flag that point to update. + - Case 4: Args with password string of length = 1, and a command_type flag that point to create. + - Case 5: Args with invalid email address, and a command_type flag that point to create. + - Case 6: Args with email_notice but no email field, and a command_type flag that point to create. + + When: + - Running validate_user_body_params. + + Then: + - Ensure that the right error was thrown. + - Case 1: Should throw an error for none-number argument. + - Case 2: Should throw an error for invalid time_zone + - Case 3: Should throw an error for missing current_password field. + - Case 4: Should throw an error for too short password string. + - Case 5: Should throw an error for invalid email address. + - Case 6: Should throw an error for missing email field. + """ + test_data = load_json("./test_data/test_validate_user_body_params.json").get(test_case, {}) + args = test_data.get('args') + command_type = test_data.get("command_type") + + with pytest.raises(Exception) as e: + validate_user_body_params(args, command_type) + + assert test_data.get('expected_error_msg') in str(e.value) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_plugin_family_command.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_plugin_family_command.json new file mode 100644 index 000000000000..f7227cec72ac --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_plugin_family_command.json @@ -0,0 +1,54 @@ +{ + "test_case_1": { + "args": {"limit": "1", "is_active": "true"}, + "mock_response": {"error_code":0,"error_msg":"","response":[{"id":"0"},{"id":"1"}],"timestamp":1684676253,"type":"regular","warnings":[]}, + "expected_hr": "### Plugin families:\n|Plugin ID|Is Active|\n|---|---|\n| 0 | true |\n", + "expected_ec": [ + { + "ID": "0" + } + ] + }, + "test_case_2": { + "args": {}, + "mock_response": {"error_code":0,"error_msg":"","response":[{"id":"0","name":"N/A"},{"id":"1","name":"test"}],"timestamp":1684676253,"type":"regular","warnings":[]}, + "expected_hr": "### Plugin families:\n|Plugin ID|Plugin Name|\n|---|---|\n| 0 | N/A |\n| 1 | test |\n", + "expected_ec": [ + { + "ID": "0", + "Name": "N/A" + }, + { + "ID": "1", + "Name": "test" + } + ] + }, + "test_case_3": { + "args": {"plugin_id": "0"}, + "mock_response": {"error_code":0,"error_msg":"","response":{"count":0,"id":"0","name":"N/A","plugins":[],"type":"compliance"},"timestamp":1684676703,"type":"regular","warnings":[]}, + "expected_hr": "### Plugin families:\n|Plugin ID|Plugin Name|\n|---|---|\n| 0 | N/A |\n", + "expected_ec": [ + { + "Count": 0, + "ID": "0", + "Name": "N/A", + "Type": "compliance" + } + ] + }, + "test_case_4": { + "args": {"plugin_id": "0"}, + "mock_response": {"error_code":0,"error_msg":"","response":{"count":0,"id":"0","name":"N/A","plugins":[],"type":"active"},"timestamp":1684676703,"type":"regular","warnings":[]}, + "expected_hr": "### Plugin families:\n|Plugin ID|Plugin Name|Is Active|\n|---|---|---|\n| 0 | N/A | true |\n", + "expected_ec": [ + { + "Count": 0, + "ID": "0", + "Name": "N/A", + "Type": "active" + } + ] + } +} + diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_user_body_params.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_user_body_params.json new file mode 100644 index 000000000000..a5d2182428e1 --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_user_body_params.json @@ -0,0 +1,32 @@ +{ + "test_case_1":{ + "args": {"group_id": "s"}, + "command_type": "create", + "expected_error_msg": "group_id must be a valid number." + }, + "test_case_2":{ + "args": {"time_zone": "time_zone"}, + "command_type": "create", + "expected_error_msg": "Invalid time zone ID. Please choose one of the following: https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html" + }, + "test_case_3":{ + "args": {"password": "s"}, + "command_type": "update", + "expected_error_msg": "current_password must be provided when attempting to update password." + }, + "test_case_4":{ + "args": {"password": "s"}, + "command_type": "create", + "expected_error_msg": "Password length must be at least 3 characters." + }, + "test_case_5":{ + "args": {"email": "email"}, + "command_type": "create", + "expected_error_msg": "Error: The given email address: email is not valid" + }, + "test_case_6":{ + "args": {"email_notice": "both"}, + "command_type": "create", + "expected_error_msg": "When email_notice is different from none, an email must be given as well." + } +} \ No newline at end of file From a50dd5915119e13b636cd3e28846c18522c12580 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Mon, 22 May 2023 11:00:45 +0300 Subject: [PATCH 033/115] pre-commit fixes --- Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py | 4 ++-- Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml | 2 +- .../Integrations/Tenable_sc/Tenable_sc_test.py | 9 --------- Packs/Tenable_sc/ReleaseNotes/1_0_10.md | 2 +- 4 files changed, 4 insertions(+), 13 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 2ec5be770b7a..0575dd61c6d9 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -936,7 +936,7 @@ def validate_user_body_params(args, command_type): if time_zone and time_zone not in pytz.all_timezones: raise DemistoException("Invalid time zone ID. Please choose one of the following: " - "https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html") + "https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html") if command_type == "create" and (auth_type == 'Ldap' or auth_type == 'saml'): args["must_change_password"] = "false" @@ -1493,7 +1493,7 @@ def validate_create_scan_inputs(args): if time_zone and time_zone not in pytz.all_timezones: raise DemistoException("Invalid time zone ID. Please choose one of the following: " - "https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html") + "https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html") if not asset_ids and not ips: raise DemistoException('Error: Assets and/or IPs must be provided') diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index 7bbd203632f5..924902e3c2c9 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -3126,7 +3126,7 @@ script: script: '-' type: python subtype: python3 - dockerimage: demisto/python3:3.10.11.59070 + dockerimage: demisto/python3:3.10.11.59581 tests: - tenable-sc-test fromversion: 5.0.0 diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py index a5fd26c26088..324ee763ebc6 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py @@ -34,15 +34,6 @@ def test_update_asset_command(mocker, test_case): assert test_data.get('expected_hr') == command_results.readable_output -# @pytest.mark.parametrize("test_case", ["test_case_1"]) -# def test_get_vulnerability_command(mocker, test_case): -# test_data = load_json("./test_data/test_update_asset_command.json").get(test_case, {}) -# args = test_data.get('args') -# mocker.patch.object(client_mocker, 'send_request', return_value=test_data.get('mock_response')) -# command_results = get_vulnerability_command(client_mocker, args) -# assert test_data.get('expected_hr') == command_results.readable_output - - @pytest.mark.parametrize("test_case", ["test_case_1", "test_case_2", "test_case_3"]) def test_list_zones_command(mocker, test_case): """ diff --git a/Packs/Tenable_sc/ReleaseNotes/1_0_10.md b/Packs/Tenable_sc/ReleaseNotes/1_0_10.md index 8180c13e25fc..fe723a34e813 100644 --- a/Packs/Tenable_sc/ReleaseNotes/1_0_10.md +++ b/Packs/Tenable_sc/ReleaseNotes/1_0_10.md @@ -5,7 +5,7 @@ - Improved implementation of the **tenable-sc-list-zones** command. - Added the following commands: **tenable-sc-list-groups**, **tenable-sc-create-user**, **tenable-sc-update-user**, **tenable-sc-delete-user**, **tenable-sc-list-plugin-family**, **tenable-sc-create-policy**, **enable-sc-list-query**, **tenable-sc-launch-scan-report**, **tenable-sc-create-remediation-scan**, **tenable-sc-update-asset**. For more information, refer to the integration readme. - Added the **Access key** and **Secret key** integration params to support a more secured instance configuration. pre-configured integration will not be affected by this change. -- Updated the Docker image to: *demisto/python3:3.10.11.59070*. +- Updated the Docker image to: *demisto/python3:3.10.11.59581*. #### Playbooks ##### Launch Scan - Tenable.sc From 784533fb3b3715d581b421b9615a3cc7abdbaafc Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Mon, 22 May 2023 13:05:29 +0300 Subject: [PATCH 034/115] tests --- .../Integrations/Tenable_sc/Tenable_sc.py | 2 +- .../Tenable_sc/Tenable_sc_test.py | 56 +++++++++++++++++++ .../test_create_user_request_body.json | 11 ++++ .../test_validate_create_scan_inputs.json | 8 +++ 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_user_request_body.json create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_create_scan_inputs.json diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 0575dd61c6d9..2844e9bf287a 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -893,7 +893,7 @@ def create_user_request_body(args): body["managedObjectsGroups"] = [{"id": int(managed_objects_group)} for managed_objects_group in args.get('managed_objects_groups').split(',')] if time_zone := args.get('time_zone'): - body["preferences"].append([{"name": "timezone", "value": time_zone, "tag": ""}]) + body["preferences"] = [{"name": "timezone", "value": time_zone, "tag": ""}] return body diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py index 324ee763ebc6..a08c26e8fe89 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py @@ -153,3 +153,59 @@ def test_validate_user_body_params(test_case): validate_user_body_params(args, command_type) assert test_data.get('expected_error_msg') in str(e.value) + + +@pytest.mark.parametrize("test_case", ["test_case_1"]) +def test_create_user_request_body(test_case): + """ + Given: + - test case that point to the relevant test case in the json test data which include: + args, and expected body + - Case 1: Args with first_name, managed_users_groups, and time_zone fields. + + When: + - Running create_user_request_body. + + Then: + - Ensure that the body was created correctly. + - Case 1: Should create a body with all the given fields, first_name should be at the root, + managed_users_groups should be a list of ID dicts, and time_zone should be a list of one dict with name, value, and tags. + """ + test_data = load_json("./test_data/test_create_user_request_body.json").get(test_case, {}) + args = test_data.get('args') + body = create_user_request_body(args) + assert test_data.get('expected_body') == body + + +@pytest.mark.parametrize("test_case", ["test_case_1", "test_case_2", "test_case_3", "test_case_4", "test_case_5", "test_case_6"]) +def test_validate_create_scan_inputs(test_case): + """ + Given: + - test case that point to the relevant test case in the json test data which include: + args, command_type flag and the expected error message. + - Case 1: Args with group_id which is not a number, and a command_type flag that point to create. + - Case 2: Args with invalid time_zone, and a command_type flag that point to create. + - Case 3: Args with only password, and a command_type flag that point to update. + - Case 4: Args with password string of length = 1, and a command_type flag that point to create. + - Case 5: Args with invalid email address, and a command_type flag that point to create. + - Case 6: Args with email_notice but no email field, and a command_type flag that point to create. + + When: + - Running validate_create_scan_inputs. + + Then: + - Ensure that the right error was thrown. + - Case 1: Should throw an error for none-number argument. + - Case 2: Should throw an error for invalid time_zone + - Case 3: Should throw an error for missing current_password field. + - Case 4: Should throw an error for too short password string. + - Case 5: Should throw an error for invalid email address. + - Case 6: Should throw an error for missing email field. + """ + test_data = load_json("./test_data/test_validate_create_scan_inputs.json").get(test_case, {}) + args = test_data.get('args') + + with pytest.raises(Exception) as e: + validate_create_scan_inputs(args) + + assert test_data.get('expected_error_msg') in str(e.value) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_user_request_body.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_user_request_body.json new file mode 100644 index 000000000000..6923e808d58e --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_user_request_body.json @@ -0,0 +1,11 @@ +{ + "test_case_1":{ + "args": { + "first_name": "test_first_name", + "managed_users_groups": "7,1,3", + "time_zone": "time_zone_test" + }, + "expected_body": {"firstname": "test_first_name", "managedUsersGroups": [{"id": "7"}, {"id": "1"}, {"id": "3"}], + "preferences": [{"name": "timezone", "value": "time_zone_test", "tag": ""}]} + } +} \ No newline at end of file diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_create_scan_inputs.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_create_scan_inputs.json new file mode 100644 index 000000000000..8ef0afba528a --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_create_scan_inputs.json @@ -0,0 +1,8 @@ +{ + "test_case":{ + "args": { + + }, + "expected_error_msg": "" + } +} \ No newline at end of file From af3a962a24e231d33e49e1d1cc564c2f96e27d5d Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Mon, 22 May 2023 15:34:49 +0300 Subject: [PATCH 035/115] more test cases --- .../Integrations/Tenable_sc/Tenable_sc.py | 82 +++++++++------- .../Integrations/Tenable_sc/Tenable_sc.yml | 2 +- .../Tenable_sc/Tenable_sc_test.py | 98 ++++++++++++++++--- .../test_create_policy_request_body.json | 12 +++ .../test_data/test_create_scan_body.json | 47 +++++++++ .../test_validate_create_scan_inputs.json | 18 +++- .../test_data/test_validate_credentials.json | 26 +++++ 7 files changed, 231 insertions(+), 54 deletions(-) create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_policy_request_body.json create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_scan_body.json create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_credentials.json diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 2844e9bf287a..961b01713ecb 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -30,10 +30,13 @@ def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str password: str = "", access_key: str = "", secret_key: str = "", url: str = ""): if not proxy: - del os.environ['HTTP_PROXY'] - del os.environ['HTTPS_PROXY'] - del os.environ['http_proxy'] - del os.environ['https_proxy'] + try: + del os.environ['HTTP_PROXY'] + del os.environ['HTTPS_PROXY'] + del os.environ['http_proxy'] + del os.environ['https_proxy'] + except Exception: + pass self.url = f"{get_server_url(url)}/rest" self.verify_ssl = verify_ssl @@ -174,7 +177,7 @@ def logout(self): """ self.send_request(path='token', method='delete') - def create_scan(self, args): + def create_scan(self, args: Dict[str, Any]): """ Send the request for create_scan_command and create_remediation_scan_command. Args: @@ -182,6 +185,11 @@ def create_scan(self, args): Returns: Dict: The response. """ + body = self.create_scan_body(args) + + return self.send_request(path='scan', method='post', body=body) + + def create_scan_body(self, args): create_scan_mapping_dict = { 'description': 'description', 'dhcpTracking': 'dhcp_tracking', @@ -222,7 +230,7 @@ def create_scan(self, args): if credentials := args.get("credentials"): body['credentials'] = [{'id': c_id} for c_id in argToList(credentials)] - if max_scan_time := args.get('max_scan_time'): + if max_scan_time := int(args.get('max_scan_time', '1')): body['maxScanTime'] = max_scan_time * 3600 if schedule := args.get('schedule'): @@ -237,7 +245,7 @@ def create_scan(self, args): start_time = args.get("start_time") repeat_rule_freq = args.get("repeat_rule_freq", "") repeat_rule_interval = int(args.get("repeat_rule_interval", 0)) - repeat_rule_by_day = argToList(args.get("repeat_rule_by_day"), "") + repeat_rule_by_day = argToList(args.get("repeat_rule_by_day", "")) timestamp_format = "%Y%m%dT%H%M%S" expected_format = "%Y-%m-%d:%H:%M:%S" try: @@ -259,7 +267,8 @@ def create_scan(self, args): "repeat_rule_by_day, or don't provide any of them.") body['schedule']['enabled'] = argToBoolean(args.get("enabled", True)) - return self.send_request(path='scan', method='post', body=body) + remove_nulls_from_dictionary(body) + return body def get_scan_results(self, scan_results_id): """ @@ -822,26 +831,7 @@ def create_policy(self, args): Returns: Dict: The response. """ - body = { - "name": args.get("policy_name"), - "description": args.get("policy_description"), - "context": "scan", - "preferences": { - "portscan_range": args.get("port_scan_range", 'default'), - "tcp_scanner": args.get("tcp_scanner"), - "syn_scanner": args.get("syn_scanner"), - "udp_scanner": args.get("udp_scanner"), - "syn_firewall_detection": args.get("syn_firewall_detection", 'Automatic (normal)') - }, - "policyTemplate": { - "id": args.get("policy_template_id", '1') - }, - } - family = {"id": args.get("family_id", "")} - if plugins_id := args.get("plugins_id"): - family["plugins"] = [{"id": id for id in plugins_id.split(',')}] - body["families"] = [family] - remove_nulls_from_dictionary(body) + body = create_policy_request_body(args) return self.send_request(path="policy", method='POST', body=body) @@ -856,7 +846,31 @@ def capitalize_first_letter(str): return str[:1].upper() + str[1:] -def create_user_request_body(args): +def create_policy_request_body(args: Dict[str, Any]): + body = { + "name": args.get("policy_name"), + "description": args.get("policy_description"), + "context": "scan", + "preferences": { + "portscan_range": args.get("port_scan_range", 'default'), + "tcp_scanner": args.get("tcp_scanner", "no"), + "syn_scanner": args.get("syn_scanner", "yes"), + "udp_scanner": args.get("udp_scanner", "no"), + "syn_firewall_detection": args.get("syn_firewall_detection", 'Automatic (normal)') + }, + "policyTemplate": { + "id": args.get("policy_template_id", '1') + }, + } + family = {"id": args.get("family_id", "")} + if plugins_id := args.get("plugins_id"): + family["plugins"] = [{"id": id for id in plugins_id.split(',')}] + body["families"] = [family] + remove_nulls_from_dictionary(body) + return body + + +def create_user_request_body(args: Dict[str, Any]): """ Create user request body for update or create user commands. Args: @@ -888,10 +902,10 @@ def create_user_request_body(args): if args.get('managed_users_groups'): body["managedUsersGroups"] = [{"id": managed_users_group} for - managed_users_group in args.get('managed_users_groups').split(',')] + managed_users_group in args.get('managed_users_groups', "").split(',')] if args.get('managed_objects_groups'): body["managedObjectsGroups"] = [{"id": int(managed_objects_group)} for - managed_objects_group in args.get('managed_objects_groups').split(',')] + managed_objects_group in args.get('managed_objects_groups', "").split(',')] if time_zone := args.get('time_zone'): body["preferences"] = [{"name": "timezone", "value": time_zone, "tag": ""}] @@ -911,7 +925,7 @@ def get_server_url(url): return url -def validate_user_body_params(args, command_type): +def validate_user_body_params(args: Dict[str, Any], command_type: str): """ Validate all given arguments are valid according to the command type (update or create). Args: @@ -1477,7 +1491,7 @@ def create_scan_command(client: Client, args: Dict[str, Any]): ) -def validate_create_scan_inputs(args): +def validate_create_scan_inputs(args: Dict[str, Any]): """ Validate all given arguments are valid for create scan command. Args: @@ -2765,7 +2779,7 @@ def test_module(client: Client, args: Dict[str, Any]): raise Exception("Authorization Error: make sure your API Key and Secret Key are correctly set") -def main(): +def main(): # pragma: no cover params = demisto.params() command = demisto.command() args = demisto.args() diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index 924902e3c2c9..00b5ecbb0235 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -697,7 +697,7 @@ script: required: false secret: false - default: false - description: 'A comma-separated list of days of the week to run the schedule at. Possible values are: SU, MO, TU, WE, TH, FR, SA.' + description: 'A comma-separated list of days of the week to run the schedule at. Possible values are: SU,MO,TU,WE,TH,FR,SA.' isArray: true name: repeat_rule_by_day required: false diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py index a08c26e8fe89..8c3ef3fe0e72 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py @@ -139,7 +139,7 @@ def test_validate_user_body_params(test_case): Then: - Ensure that the right error was thrown. - Case 1: Should throw an error for none-number argument. - - Case 2: Should throw an error for invalid time_zone + - Case 2: Should throw an error for invalid time_zone. - Case 3: Should throw an error for missing current_password field. - Case 4: Should throw an error for too short password string. - Case 5: Should throw an error for invalid email address. @@ -160,7 +160,7 @@ def test_create_user_request_body(test_case): """ Given: - test case that point to the relevant test case in the json test data which include: - args, and expected body + args, and expected body. - Case 1: Args with first_name, managed_users_groups, and time_zone fields. When: @@ -177,30 +177,24 @@ def test_create_user_request_body(test_case): assert test_data.get('expected_body') == body -@pytest.mark.parametrize("test_case", ["test_case_1", "test_case_2", "test_case_3", "test_case_4", "test_case_5", "test_case_6"]) +@pytest.mark.parametrize("test_case", ["test_case_1", "test_case_2", "test_case_3"]) def test_validate_create_scan_inputs(test_case): """ Given: - test case that point to the relevant test case in the json test data which include: - args, command_type flag and the expected error message. - - Case 1: Args with group_id which is not a number, and a command_type flag that point to create. - - Case 2: Args with invalid time_zone, and a command_type flag that point to create. - - Case 3: Args with only password, and a command_type flag that point to update. - - Case 4: Args with password string of length = 1, and a command_type flag that point to create. - - Case 5: Args with invalid email address, and a command_type flag that point to create. - - Case 6: Args with email_notice but no email field, and a command_type flag that point to create. + args, and the expected error message. + - Case 1: Args with invalid time_zone. + - Case 2: Empty args. + - Case 3: args with ip_list and schedule = 'schedule'. When: - Running validate_create_scan_inputs. Then: - Ensure that the right error was thrown. - - Case 1: Should throw an error for none-number argument. - - Case 2: Should throw an error for invalid time_zone - - Case 3: Should throw an error for missing current_password field. - - Case 4: Should throw an error for too short password string. - - Case 5: Should throw an error for invalid email address. - - Case 6: Should throw an error for missing email field. + - Case 1: Should throw an error for invalid time_zone. + - Case 2: Should throw an error for missing ip_list and assets. + - Case 3: Should throw an error for missing dependent scan ID. """ test_data = load_json("./test_data/test_validate_create_scan_inputs.json").get(test_case, {}) args = test_data.get('args') @@ -209,3 +203,75 @@ def test_validate_create_scan_inputs(test_case): validate_create_scan_inputs(args) assert test_data.get('expected_error_msg') in str(e.value) + + +@pytest.mark.parametrize("test_case", ["test_case_1", "test_case_2", "test_case_3", "test_case_4"]) +def test_validate_credentials(test_case): + """ + Given: + - test case that point to the relevant test case in the json test data which include: + args, and the expected error message. + - Case 1: Only access key filled. + - Case 2: Only username filled. + - Case 3: No argument filled. + - Case 4: args with access key and username filled. + + When: + - Running Client creator. + + Then: + - Ensure that an error for missing credentials pair is thrown. + """ + test_data = load_json("./test_data/test_validate_credentials.json").get(test_case, {}) + access_key = test_data.get('access_key') + secret_key = test_data.get('secret_key') + user_name = test_data.get('user_name') + password = test_data.get('password') + + with pytest.raises(Exception) as e: + Client(access_key=access_key, secret_key=secret_key, user_name=user_name, password=password) + + assert "Please provide either user_name and password or secret_key and access_key" in str(e.value) + + +@pytest.mark.parametrize("test_case", ["test_case_1"]) +def test_create_policy_request_body(test_case): + """ + Given: + - test case that point to the relevant test case in the json test data which include: + args, and expected body. + - Case 1: Args with policy_description, policy_template_id, family_id,and plugins_id. + + When: + - Running create_policy_request_body. + + Then: + - Ensure that the body was created correctly. + - Case 1: Should create a body with all the given fields, and include default none-included (preference and context). + """ + test_data = load_json("./test_data/test_create_policy_request_body.json").get(test_case, {}) + args = test_data.get('args') + body = create_policy_request_body(args) + assert test_data.get('expected_body') == body + + +@pytest.mark.parametrize("test_case", ["test_case_1", "test_case_2", "test_case_3"]) +def test_create_scan_body(mocker, test_case): + """ + Given: + - test case that point to the relevant test case in the json test data which include: + args, and expected body. + - Case 1: Args with policy_description, policy_template_id, family_id,and plugins_id. + + When: + - Running create_scan_body. + + Then: + - Ensure that the body was created correctly. + - Case 1: Should create a body with all the given fields, and include default none-included (preference and context). + """ + test_data = load_json("./test_data/test_create_scan_body.json").get(test_case, {}) + args = test_data.get('args') + mocker.patch.object(client_mocker, 'send_request', return_value=test_data.get('mock_response', {})) + body = client_mocker.create_scan_body(args) + assert test_data.get('expected_body') == body diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_policy_request_body.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_policy_request_body.json new file mode 100644 index 000000000000..3a97b956fcfa --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_policy_request_body.json @@ -0,0 +1,12 @@ +{ + "test_case_1":{ + "args": { + "policy_description": "test_description", + "policy_template_id": "2", + "family_id": "family_id", + "plugins_id": "2,3" + }, + "expected_body": {"description": "test_description", "context": "scan", "preferences": {"portscan_range": "default", "tcp_scanner": "no", "syn_scanner": "yes", "udp_scanner": "no", + "syn_firewall_detection": "Automatic (normal)"}, "policyTemplate": {"id": "2"}, "families": [{"id": "family_id", "plugins": [{"id": "3"}]}]} + } +} \ No newline at end of file diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_scan_body.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_scan_body.json new file mode 100644 index 000000000000..4a4af58bde6e --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_scan_body.json @@ -0,0 +1,47 @@ +{ + "test_case_1":{ + "args": { + "description": "scan_description", + "scan_type": "scan_type", + "name": "scan_name", + "repository_id": "repository_id", + "asset_ids": "AllManageable", + "schedule": "rollover", + "dependent_id": "1" + }, + "expected_body": {"description": "scan_description", "type": "scan_type", "name": "scan_name", "repository": {"id": "repository_id"}, "assets": [{"id": "1"}, {"id": "2"}], + "maxScanTime": 3600, "schedule": {"type": "rollover", "dependentID": "1"}}, + "mock_response": {"error_code":0,"error_msg":"","response":{ + "manageable":[ + {"description":"asset_1_description","groups":[],"id":"1","ipCount":122,"modifiedTime":"1684660372","name":"asset_1_name","owner":{ + "firstname":"test","id":"1","lastname":"","username":"test","uuid":"uuid"}, + "tags":"","type":"dynamic","uuid":"uuid"}, + {"description":"Asset test", "groups":[],"id":"2","ipCount":159,"modifiedTime":"1673270083","name":"Systems that have been Scanned","owner": + {"firstname":"test","id":"1","lastname":"","username":"test","uuid":"uuid"}, "tags":"","type":"dynamic","uuid":"uuid"}]}} + }, + "test_case_2":{ + "args": { + "policy_id": "1", + "scan_name": "scan_name", + "credentials": "1,2,3", + "max_scan_time": "5", + "schedule": "ical", + "time_zone": "time_zone", + "start_time": "2023-09-09:07:30:00", + "repeat_rule_freq": "DAILY", + "repeat_rule_interval": "8", + "repeat_rule_by_day": "SU,MO" + }, + "expected_body": {"type": "policy", "name": "scan_name", "policy": {"id": "1"}, "credentials": [{"id": "1"}, {"id": "2"}, {"id": "3"}], + "maxScanTime": 18000, "schedule": {"type": "ical", "start": "TZID=20230909T073000:20230909T073000", "repeatRule": "FREQ=DAILY;INTERVAL=8;", "enabled": true}} + }, + "test_case_3":{ + "args": { + "plugins_id": "1", + "zone_id": "zone_id", + "report_ids": "1,2,3" + }, + "expected_body": {"type": "plugin", "pluginID": "1", "zone": {"id": "zone_id"}, + "reports": [{"id": "1", "reportSource": "individual"}, {"id": "2", "reportSource": "individual"}, {"id": "3", "reportSource": "individual"}], "maxScanTime": 3600} + } +} \ No newline at end of file diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_create_scan_inputs.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_create_scan_inputs.json index 8ef0afba528a..d7a2835de6eb 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_create_scan_inputs.json +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_create_scan_inputs.json @@ -1,8 +1,20 @@ { - "test_case":{ + "test_case_1":{ "args": { - + "time_zone": "incorrect_time_zone" }, - "expected_error_msg": "" + "expected_error_msg": "Invalid time zone ID. Please choose one of the following: https://docs.oracle.com/middleware/1221/wcs/tag-ref/MISC/TimeZones.html" + }, + "test_case_2":{ + "args": { + }, + "expected_error_msg": "Error: Assets and/or IPs must be provided" + }, + "test_case_3":{ + "args": { + "schedule": "dependent", + "ip_list": "ip" + }, + "expected_error_msg": "Error: Dependent schedule must include a dependent scan ID" } } \ No newline at end of file diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_credentials.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_credentials.json new file mode 100644 index 000000000000..26b1821f4478 --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_credentials.json @@ -0,0 +1,26 @@ +{ + "test_case_1":{ + "access_key": "access_key", + "secret_key": "", + "user_name": "", + "password": "" + }, + "test_case_2":{ + "access_key": "", + "secret_key": "", + "user_name": "user_name", + "password": "" + }, + "test_case_3":{ + "access_key": "", + "secret_key": "", + "user_name": "", + "password": "" + }, + "test_case_4":{ + "access_key": "access_key", + "secret_key": "", + "user_name": "user_name", + "password": "" + } +} \ No newline at end of file From 2903ea59f233ad39f100e683c889f706e822aeb7 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Mon, 22 May 2023 15:49:06 +0300 Subject: [PATCH 036/115] tests --- .../Integrations/Tenable_sc/Tenable_sc_test.py | 16 +++++++++++++--- .../test_data/test_create_scan_body.json | 6 ++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py index 8c3ef3fe0e72..6ebdbbaac6e2 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py @@ -260,15 +260,25 @@ def test_create_scan_body(mocker, test_case): """ Given: - test case that point to the relevant test case in the json test data which include: - args, and expected body. - - Case 1: Args with policy_description, policy_template_id, family_id,and plugins_id. + args, mock_response (for some test cases) and expected body. + - Case 1: Args with scan_type, scan_name saved under "name", repository_id, asset_ids set to "AllManageable", + schedule set to "rollover", and dependent_id. Also, a mock_response to get asses with two manageable assets. + - Case 2: Args with policy_id, scan_name saved under "scan_name", credentials with few credentials, max_scan_time, + schedule set to "ical", time_zone, start_time, repeat_rule_freq, repeat_rule_interval, and repeat_rule_by_day. + - Case 3: Args with plugins_id, zone_id and a comma separated list of report_ids. When: - Running create_scan_body. Then: - Ensure that the body was created correctly. - - Case 1: Should create a body with all the given fields, and include default none-included (preference and context). + - Case 1: Should set type to be scan_type value, configure scan_name, add repository_id under repository dict, + send a get_assets request and include the two assets from the response in the request body, + set max_scan_time to 3600, and include dependent_id under schedule. + - Case 2: Should set type to be policy, configure scan_name, create a list of credentials id dicts, + calculate max_scan_time, and include time_zone, start_time, repeat_rule_freq, repeat_rule_interval, + and repeat_rule_by_day under schedule. + - Case 3: Should set type to be plugin, create a zone dict, a list of report ids dicts and set max_scan_time to 3600. """ test_data = load_json("./test_data/test_create_scan_body.json").get(test_case, {}) args = test_data.get('args') diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_scan_body.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_scan_body.json index 4a4af58bde6e..e37406437fdd 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_scan_body.json +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_scan_body.json @@ -1,7 +1,6 @@ { "test_case_1":{ "args": { - "description": "scan_description", "scan_type": "scan_type", "name": "scan_name", "repository_id": "repository_id", @@ -9,7 +8,7 @@ "schedule": "rollover", "dependent_id": "1" }, - "expected_body": {"description": "scan_description", "type": "scan_type", "name": "scan_name", "repository": {"id": "repository_id"}, "assets": [{"id": "1"}, {"id": "2"}], + "expected_body": {"type": "scan_type", "name": "scan_name", "repository": {"id": "repository_id"}, "assets": [{"id": "1"}, {"id": "2"}], "maxScanTime": 3600, "schedule": {"type": "rollover", "dependentID": "1"}}, "mock_response": {"error_code":0,"error_msg":"","response":{ "manageable":[ @@ -37,11 +36,10 @@ }, "test_case_3":{ "args": { - "plugins_id": "1", "zone_id": "zone_id", "report_ids": "1,2,3" }, - "expected_body": {"type": "plugin", "pluginID": "1", "zone": {"id": "zone_id"}, + "expected_body": {"type": "plugin", "zone": {"id": "zone_id"}, "reports": [{"id": "1", "reportSource": "individual"}, {"id": "2", "reportSource": "individual"}, {"id": "3", "reportSource": "individual"}], "maxScanTime": 3600} } } \ No newline at end of file From d05b8d2977149d8c8875d597d1eb0d632b139225 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Tue, 23 May 2023 17:25:16 +0300 Subject: [PATCH 037/115] add more tests --- .../Integrations/Tenable_sc/Tenable_sc.py | 98 +++++++++++----- .../Tenable_sc/Tenable_sc_test.py | 108 +++++++++++++++++- ...te_get_device_request_params_and_path.json | 30 +++++ ...create_get_vulnerability_request_body.json | 36 ++++++ .../Tenable_sc/test_data/test_get_query.json | 8 ++ .../test_data/test_list_queries.json | 26 +++++ 6 files changed, 275 insertions(+), 31 deletions(-) create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_get_device_request_params_and_path.json create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_get_vulnerability_request_body.json create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_get_query.json create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_queries.json diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 961b01713ecb..2d4c03076ee8 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -190,6 +190,13 @@ def create_scan(self, args: Dict[str, Any]): return self.send_request(path='scan', method='post', body=body) def create_scan_body(self, args): + """ + Construct the body for the create_scan request. + Args: + args (dict): The demisto.args() object. + Returns: + Dict: The request body. + """ create_scan_mapping_dict = { 'description': 'description', 'dhcpTracking': 'dhcp_tracking', @@ -574,7 +581,7 @@ def delete_query(self, query_id): path = 'query/' + str(query_id) self.send_request(path, method='delete') - def get_analysis(self, query=None, scan_results_id=None, args={}): + def get_analysis(self, query=None, scan_results_id=None, args={}, calling_command="get_vulnerability_command"): """ Send the request for get_vulnerability_command and get_vulnerabilities. Args: @@ -582,6 +589,7 @@ def get_analysis(self, query=None, scan_results_id=None, args={}): or as an ID of an existing query (as in get_vulnerabilities). scan_results_id (None or str): str if received from get_vulnerabilities, otherwise will be part of args. args (dict): Either an empty dict if passed from get_vulnerabilities, otherwise, the demisto.results() object. + calling_command (str): The command that call this function. Returns: Dict: The response. """ @@ -589,7 +597,8 @@ def get_analysis(self, query=None, scan_results_id=None, args={}): return self.send_request(path='analysis', method='post', body=body) - def create_get_vulnerability_request_body(self, args={}, query=None, scan_results_id=None): + def create_get_vulnerability_request_body(self, args={}, query=None, scan_results_id=None, + calling_command="get_vulnerability_command"): """ Create the body for the request made in get_analysis. Args: @@ -597,34 +606,43 @@ def create_get_vulnerability_request_body(self, args={}, query=None, scan_result or as an ID of an existing query (as in get_vulnerabilities). scan_results_id (None or str): str if received from get_vulnerabilities, otherwise will be part of args. args (dict): Either an empty dict if passed from get_vulnerabilities, otherwise, the demisto.results() object. + calling_command (str): The command that call this function. Returns: Dict: The prepared request body. """ vuln_id = args.get('vulnerability_id') scan_results_id = scan_results_id or args.get('scan_results_id') - sort_field = args.get('sort_field') + sort_field = args.get('sort_field', 'severity') query_id = query or args.get('query_id') if not isinstance(query_id, dict): query = {'id': query_id} - sort_direction = args.get('sort_direction') + else: + query = query_id + sort_direction = args.get('sort_direction', "ASC") source_type = args.get('source_type', "individual") page = int(args.get('page', '0')) limit = int(args.get('limit', '50')) if limit > 200: limit = 200 body = { - 'tool': 'vulndetails', 'type': 'vuln', - 'startOffset': page, # Lower bound for the results list (must be specified) - 'endOffset': page + limit, # Upper bound for the results list (must be specified) - 'sortField': sort_field, - 'sortDir': sort_direction, - 'sourceType': source_type, 'view': 'all', + 'sourceType': source_type, } + if calling_command == "get_vulnerability_command": + body.update({ + 'startOffset': page, # Lower bound for the results list (must be specified) + 'endOffset': page + limit, # Upper bound for the results list (must be specified) + 'sortField': sort_field, + 'sortDir': sort_direction, + 'tool': 'vulndetails', + }) if source_type == 'individual': if scan_results_id: body['scanID'] = scan_results_id + else: + raise DemistoException("When choosing source_type = individual - scan_results_id must be provided.") + if calling_command == "get_vulnerability_command": vuln_filter = [{ 'filterName': 'pluginID', 'operator': '=', @@ -633,8 +651,6 @@ def create_get_vulnerability_request_body(self, args={}, query=None, scan_result query["filters"] = vuln_filter query["tool"] = 'vulndetails' query["type"] = 'vuln' - else: - raise DemistoException("When choosing source_type = individual - scan_results_id must be provided.") else: body['sourceType'] = source_type if not query_id: @@ -711,18 +727,7 @@ def get_device(self, uuid, ip, dns_name, repo): Returns: Dict: The response. """ - path = 'repository/' + repo + '/' if repo else '' - path += 'deviceInfo' - params = { - 'fields': 'ip,uuid,macAddress,netbiosName,dnsName,os,osCPE,lastScan,repository,total,severityLow,' - 'severityMedium,severityHigh,severityCritical' - } - if uuid: - params['uuid'] = uuid - else: - params['ip'] = ip - if dns_name: - params['dnsName'] = dns_name + path, params = create_get_device_request_params_and_path(uuid, ip, dns_name, repo) return self.send_request(path, params=params) @@ -839,14 +844,41 @@ def create_policy(self, args): ''' HELPER FUNCTIONS ''' -def capitalize_first_letter(str): - if str == 'id': - return 'ID' +def create_get_device_request_params_and_path(uuid: str, ip: str, dns_name: str, repo: str): + """ + Construct the url suffix and params dict for get_device request. + Args: + uuid (str): UUID extracted from args. + ip (str): IP extracted from args. + dns_name (str): Dns extracted from args. + repo (str): Repo name extracted from args. + Returns: + str: The url suffix for the request. + Dict: The params for the request. + """ + path = f'repository/{repo}/' if repo else '' + path += 'deviceInfo' + params = { + 'fields': 'ip,uuid,macAddress,netbiosName,dnsName,os,osCPE,lastScan,repository,total,severityLow,' + 'severityMedium,severityHigh,severityCritical' + } + if uuid: + params['uuid'] = uuid else: - return str[:1].upper() + str[1:] + params['ip'] = ip + if dns_name: + params['dnsName'] = dns_name + return path, params def create_policy_request_body(args: Dict[str, Any]): + """ + Construct the body for create_policy request. + Args: + args (Dict): The demisto.args() object. + Returns: + Dict: The body for the request. + """ body = { "name": args.get("policy_name"), "description": args.get("policy_description"), @@ -1738,7 +1770,8 @@ def get_vulnerabilities(client: Client, scan_results_id): if not query or 'response' not in query: return 'Could not get vulnerabilites query' - analysis = client.get_analysis(query=query['response']['id'], scan_results_id=scan_results_id) + analysis = client.get_analysis(query=query['response']['id'], scan_results_id=scan_results_id, + calling_command="get_vulnerabilities") client.delete_query(query.get('response', {}).get('id')) @@ -2748,13 +2781,18 @@ def list_queries(client: Client, type): "Query Name": usable_query.get("name"), "Query Description": usable_query.get("description"), "Query Filters": usable_query.get("filters"), - "Query Usable": "True" + "Query Usable": "True", + "Query Manageable": "False" }) else: for mapped_query in mapped_queries: if query_id == mapped_query["Query Id"]: mapped_query["Query Usable"] = "True" + for mapped_query in mapped_queries: + if not mapped_query.get("Query Usable"): + mapped_query["Query Usable"] = "False" + mapped_queries.extend(mapped_usable_queries) headers = ["Query Id", "Query Name", "Query Description", "Query Filters", "Query Manageable", "Query Usable"] hr = tableToMarkdown('Queries:', mapped_queries, headers, removeNull=True) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py index 6ebdbbaac6e2..be8de436c90c 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py @@ -278,10 +278,116 @@ def test_create_scan_body(mocker, test_case): - Case 2: Should set type to be policy, configure scan_name, create a list of credentials id dicts, calculate max_scan_time, and include time_zone, start_time, repeat_rule_freq, repeat_rule_interval, and repeat_rule_by_day under schedule. - - Case 3: Should set type to be plugin, create a zone dict, a list of report ids dicts and set max_scan_time to 3600. + - Case 3: Should set type to be plugin, create a zone dict, a list of report ids dicts and set max_scan_time to 3600. """ test_data = load_json("./test_data/test_create_scan_body.json").get(test_case, {}) args = test_data.get('args') mocker.patch.object(client_mocker, 'send_request', return_value=test_data.get('mock_response', {})) body = client_mocker.create_scan_body(args) assert test_data.get('expected_body') == body + + +@pytest.mark.parametrize("test_case", ["test_case_1", "test_case_2", "test_case_3"]) +def test_create_get_device_request_params_and_path(test_case): + """ + Given: + - test case that point to the relevant test case in the json test data which include: + function args (uuid, ip, dns_name, repo), expected path and params, and expected body. + - Case 1: repo, uuid and dns_name. + - Case 2: ip. + - Case 3: ip and dns_name. + + When: + - Running create_get_device_request_params_and_path. + + Then: + - Ensure that the body was created correctly. + - Case 1: Path should include repo, params should include uuid and ignore dns_name. + - Case 2: Path should be deviceInfo, params should include ip. + - Case 3: Path should be deviceInfo, params should include ip and dns_name. + """ + test_data = load_json("./test_data/test_create_get_device_request_params_and_path.json").get(test_case, {}) + uuid = test_data.get('uuid') + ip = test_data.get('ip') + dns_name = test_data.get('dns_name') + repo = test_data.get('repo') + path, params = create_get_device_request_params_and_path(uuid, ip, dns_name, repo) + assert test_data.get('expected_path') == path + assert test_data.get('expected_params') == params + + +@pytest.mark.parametrize("test_case", ["test_case_1", "test_case_2", "test_case_3"]) +def test_create_get_vulnerability_request_body(mocker, test_case): + """ + Given: + - test case that point to the relevant test case in the json test data which include: + args, query, scan_results_id, calling_command, and expected body. + - Case 1: Args with scan_results_id, vulnerability_id, sort_field, and sort_direction. + Empty query, scan_results_id, and calling_command. + - Case 2: Args with scan_results_id, vulnerability_id, query_id, source_type and limit higher than 200. + Empty query, scan_results_id, and calling_command. + - Case 3: Empty args, filled query and scan_results_id, and calling_command = get_vulnerabilities. + + When: + - Running create_get_vulnerability_request_body. + + Then: + - Ensure that the body was created correctly. + - Case 1: Should complete all the none-given fields, count source_type as individual, and add related fields. + - Case 2: Should lower limit to 200, ignore scan_results_id and vulnerability_id. + - Case 3: Shouldn't handle any filtering fields, only create the basic body. + """ + test_data = load_json("./test_data/test_create_get_vulnerability_request_body.json").get(test_case, {}) + args = test_data.get('args') + query = test_data.get('query') + scan_results_id = test_data.get('scan_results_id') + calling_command = test_data.get("calling_command", "get_vulnerability_command") + body = client_mocker.create_get_vulnerability_request_body(args, query, scan_results_id, calling_command) + assert test_data.get('expected_body') == body + + +@pytest.mark.parametrize("test_case", ["test_case_1"]) +def test_get_query(mocker, test_case): + """ + Given: + - test case that point to the relevant test case in the json test data which include: + query_id, response mock, expected hr, and expected_ec. + - Case 1: response mock that misses filters section + + When: + - Running get_query. + + Then: + - Ensure that the response was parsed correctly and right HR and EC is returned. + - Case 1: Should exclude the filters section from the HR but not from the EC. + """ + test_data = load_json("./test_data/test_get_query.json").get(test_case, {}) + query_id = test_data.get('query_id') + mocker.patch.object(client_mocker, 'send_request', return_value=test_data.get('mock_response')) + _, hr, query = get_query(client_mocker, query_id) + assert test_data.get('expected_hr') == hr + assert test_data.get('expected_ec') == query + + +@pytest.mark.parametrize("test_case", ["test_case_1"]) +def test_list_queries(mocker, test_case): + """ + Given: + - test case that point to the relevant test case in the json test data which include: + response mock, expected hr, and expected_ec. + - Case 1: response mock with 2 manageable and 2 usable queries, one of the queries appears in both lists, + some queries with filters and some don't. + + When: + - Running list_queries. + + Then: + - Ensure that the response was parsed correctly and right HR and EC is returned. + - Case 1: Should Include the filters section in the HR, + should have "True" in both manageable and usable columns for the mutual query and fill False in the none-mutual queries. + """ + test_data = load_json("./test_data/test_list_queries.json").get(test_case, {}) + mocker.patch.object(client_mocker, 'send_request', return_value=test_data.get('mock_response')) + _, hr, query = list_queries(client_mocker, "") + assert test_data.get('expected_hr') == hr + assert test_data.get('expected_ec') == query diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_get_device_request_params_and_path.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_get_device_request_params_and_path.json new file mode 100644 index 000000000000..c26e1618685b --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_get_device_request_params_and_path.json @@ -0,0 +1,30 @@ +{ + "test_case_1":{ + "repo": "repo", + "uuid": "uuid", + "dns_name": "dns_name", + "expected_path": "repository/repo/deviceInfo", + "expected_params": { + "fields": "ip,uuid,macAddress,netbiosName,dnsName,os,osCPE,lastScan,repository,total,severityLow,severityMedium,severityHigh,severityCritical", + "uuid": "uuid" + } + }, + "test_case_2":{ + "ip": "ip", + "expected_path": "deviceInfo", + "expected_params": { + "fields": "ip,uuid,macAddress,netbiosName,dnsName,os,osCPE,lastScan,repository,total,severityLow,severityMedium,severityHigh,severityCritical", + "ip": "ip" + } + }, + "test_case_3":{ + "ip": "ip", + "dns_name": "dns_name", + "expected_path": "deviceInfo", + "expected_params": { + "fields": "ip,uuid,macAddress,netbiosName,dnsName,os,osCPE,lastScan,repository,total,severityLow,severityMedium,severityHigh,severityCritical", + "ip": "ip", + "dnsName": "dns_name" + } + } +} \ No newline at end of file diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_get_vulnerability_request_body.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_get_vulnerability_request_body.json new file mode 100644 index 000000000000..6a1fc967155a --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_get_vulnerability_request_body.json @@ -0,0 +1,36 @@ +{ + "test_case_1":{ + "args": { + "scan_results_id": "scan_results_id", + "vulnerability_id": "vulnerability_id", + "sort_field": "sort_field", + "sort_direction": "DESC" + }, + "scan_results_id": "", + "query": {}, + "expected_body": {"tool": "vulndetails", "type": "vuln", "startOffset": 0, "endOffset": 50, "sortField": "sort_field", "sortDir": "DESC", "sourceType": "individual", "view": "all", + "scanID": "scan_results_id", + "query": {"id": null, "filters": [{"filterName": "pluginID", "operator": "=", "value": "vulnerability_id"}], "tool": "vulndetails", "type": "vuln"}} + }, + "test_case_2":{ + "args": { + "scan_results_id": "scan_results_id", + "vulnerability_id": "vulnerability_id", + "query_id": "1", + "source_type": "patched", + "limit": "250" + }, + "scan_results_id": "", + "query": {}, + "expected_body": {"tool": "vulndetails", "type": "vuln", "startOffset": 0, "endOffset": 200, "sortField": "severity", "sortDir": "ASC", "sourceType": "patched", + "view": "all", "query": {"id": "1"}} + }, + "test_case_3":{ + "args": { + }, + "scan_results_id": "scan_results_id", + "query": {"query_id": "1"}, + "calling_command": "get_vulnerabilities", + "expected_body": {"type": "vuln", "view": "all", "sourceType": "individual", "scanID": "scan_results_id", "query": {"query_id": "1"}} + } +} \ No newline at end of file diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_get_query.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_get_query.json new file mode 100644 index 000000000000..d03ff416162f --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_get_query.json @@ -0,0 +1,8 @@ +{ + "test_case_1": { + "query_id": "test_id", + "mock_response": {"error_code":0,"error_msg":"","response":{"id": "test_id", "name": "test_name", "description": "test_description", "filters": null}, "timestamp":1684676253,"type":"regular","warnings":[]}, + "expected_hr": "### Query test_id\n|Query Id|Query Name|Query Description|\n|---|---|---|\n| test_id | test_name | test_description |\n", + "expected_ec": {"id": "test_id", "name": "test_name", "description": "test_description", "filters": null} + } +} \ No newline at end of file diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_queries.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_queries.json new file mode 100644 index 000000000000..59122b872358 --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_queries.json @@ -0,0 +1,26 @@ +{ + "test_case_1": { + "query_id": "test_id", + "mock_response": {"error_code":0,"error_msg":"", + "response": { + "manageable": [ + { + "id": "1", "name": "test_name", "description": "test_description", "filters": "filter" + }, + { + "id": "2", "name": "test_name", "description": "test_description", "filters": null + } + ], + "usable": [ + { + "id": "1", "name": "test_name", "description": "test_description", "filters": "filter" + }, + { + "id": "3", "name": "test_name", "description": "test_description", "filters": null + } + ]}, "timestamp":1684676253,"type":"regular","warnings":[]}, + "expected_hr": "### Queries:\n|Query Id|Query Name|Query Description|Query Filters|Query Manageable|Query Usable|\n|---|---|---|---|---|---|\n| 1 | test_name | test_description | filter | True | True |\n| 2 | test_name | test_description | | True | False |\n| 3 | test_name | test_description | | False | True |\n", + "expected_ec": {"manageable": [{"id": "1", "name": "test_name", "description": "test_description", "filters": "filter"}, {"id": "2", "name": "test_name", "description": "test_description", "filters": null}], + "usable": [{"id": "1", "name": "test_name", "description": "test_description", "filters": "filter"}, {"id": "3", "name": "test_name", "description": "test_description", "filters": null}]} + } +} \ No newline at end of file From 88203d73819848f767b8230d7706b27d072a4453 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Tue, 23 May 2023 17:39:51 +0300 Subject: [PATCH 038/115] docker update --- Packs/Tenable_sc/ReleaseNotes/1_0_10.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Packs/Tenable_sc/ReleaseNotes/1_0_10.md b/Packs/Tenable_sc/ReleaseNotes/1_0_10.md index fe723a34e813..bc20d5716611 100644 --- a/Packs/Tenable_sc/ReleaseNotes/1_0_10.md +++ b/Packs/Tenable_sc/ReleaseNotes/1_0_10.md @@ -5,7 +5,7 @@ - Improved implementation of the **tenable-sc-list-zones** command. - Added the following commands: **tenable-sc-list-groups**, **tenable-sc-create-user**, **tenable-sc-update-user**, **tenable-sc-delete-user**, **tenable-sc-list-plugin-family**, **tenable-sc-create-policy**, **enable-sc-list-query**, **tenable-sc-launch-scan-report**, **tenable-sc-create-remediation-scan**, **tenable-sc-update-asset**. For more information, refer to the integration readme. - Added the **Access key** and **Secret key** integration params to support a more secured instance configuration. pre-configured integration will not be affected by this change. -- Updated the Docker image to: *demisto/python3:3.10.11.59581*. +- Updated the Docker image to: *demisto/python3:3.10.11.61265*. #### Playbooks ##### Launch Scan - Tenable.sc From 8c63dfd91aedfa04e94be46e18f227740c3879c9 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Tue, 23 May 2023 17:41:55 +0300 Subject: [PATCH 039/115] docker update --- Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py | 4 ++-- Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 2d4c03076ee8..8febf704480c 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -35,8 +35,8 @@ def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str del os.environ['HTTPS_PROXY'] del os.environ['http_proxy'] del os.environ['https_proxy'] - except Exception: - pass + except Exception as e: + demisto.debug(f"Encountered the following error: {e}") self.url = f"{get_server_url(url)}/rest" self.verify_ssl = verify_ssl diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index 00b5ecbb0235..e9b6ff9879ae 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -3126,7 +3126,7 @@ script: script: '-' type: python subtype: python3 - dockerimage: demisto/python3:3.10.11.59581 + dockerimage: demisto/python3:3.10.11.61265 tests: - tenable-sc-test fromversion: 5.0.0 From 5134d5af174af69b7d6812536a9a7a98886213b9 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Wed, 24 May 2023 13:58:35 +0300 Subject: [PATCH 040/115] added tests --- .../test_data/test_launch_scan_errors.json | 41 +++++++++++++++++ .../test_data/test_list_query_command.json | 34 ++++++++++++++ .../test_list_report_definitions_command.json | 14 ++++++ .../test_data/test_list_users_command.json | 45 +++++++++++++++++++ 4 files changed, 134 insertions(+) create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_launch_scan_errors.json create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_query_command.json create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_report_definitions_command.json create mode 100644 Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_users_command.json diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_launch_scan_errors.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_launch_scan_errors.json new file mode 100644 index 000000000000..e7c1f6871b83 --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_launch_scan_errors.json @@ -0,0 +1,41 @@ +{ + "test_case_1":{ + "args": { + "scan_id": "scan_id", + "diagnostic_target": "diagnostic_target" + }, + "expected_error_msg": "Error: If one of diagnostic target or password is provided, both of them must be provided." + }, + "test_case_2":{ + "args": { + "scan_id": "scan_id", + "diagnostic_password": "diagnostic_password" + }, + "expected_error_msg": "Error: If one of diagnostic target or password is provided, both of them must be provided." + }, + "test_case_3":{ + "args": { + "scan_id": "scan_id", + "diagnostic_target": "diagnostic_target", + "diagnostic_password": "diagnostic_password" + }, + "mock_response": {}, + "expected_error_msg": "Error: Could not retrieve the scan." + }, + "test_case_4":{ + "args": { + "scan_id": "scan_id" + }, + "mock_response": {"response": {}}, + "expected_error_msg": "Error: Could not retrieve the scan." + }, + "test_case_5":{ + "args": { + "scan_id": "scan_id", + "diagnostic_target": "diagnostic_target", + "diagnostic_password": "diagnostic_password" + }, + "mock_response": {"response": {"description": "response without"}}, + "expected_error_msg": "Error: Could not retrieve the scan." + } +} \ No newline at end of file diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_query_command.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_query_command.json new file mode 100644 index 000000000000..14e81644db11 --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_query_command.json @@ -0,0 +1,34 @@ +{ + "test_case_1": { + "args": {}, + "mocked_function": "list_queries", + "mock_response": {"error_code":0,"error_msg":"", + "response": { + "manageable": [ + { + "id": "1", "name": "test_name", "description": "test_description", "filters": "filter" + }, + { + "id": "2", "name": "test_name", "description": "test_description", "filters": null + } + ], + "usable": [ + { + "id": "1", "name": "test_name", "description": "test_description", "filters": "filter" + }, + { + "id": "3", "name": "test_name", "description": "test_description", "filters": null + } + ]}, "timestamp":1684676253,"type":"regular","warnings":[]}, + "expected_hr": "### Queries:\n|Query Id|Query Name|Query Description|Query Filters|Query Manageable|Query Usable|\n|---|---|---|---|---|---|\n| 1 | test_name | test_description | filter | True | True |\n| 2 | test_name | test_description | | True | False |\n| 3 | test_name | test_description | | False | True |\n", + "expected_ec": {"Manageable": [{"ID": "1", "Name": "test_name", "Description": "test_description", "Filters": "filter"}, {"ID": "2", "Name": "test_name", "Description": "test_description", "Filters": null}], + "Usable": [{"ID": "1", "Name": "test_name", "Description": "test_description", "Filters": "filter"}, {"ID": "3", "Name": "test_name", "Description": "test_description", "Filters": null}]} + }, + "test_case_2": { + "args": {"query_id": "test_id"}, + "mocked_function": "get_query", + "mock_response": {"error_code":0,"error_msg":"","response":{"id": "test_id", "name": "test_name", "description": "test_description", "filters": null}, "timestamp":1684676253,"type":"regular","warnings":[]}, + "expected_hr": "### Query test_id\n|Query Id|Query Name|Query Description|\n|---|---|---|\n| test_id | test_name | test_description |\n", + "expected_ec": {"ID": "test_id", "Name": "test_name", "Description": "test_description"} + } +} \ No newline at end of file diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_report_definitions_command.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_report_definitions_command.json new file mode 100644 index 000000000000..fb37c21b3e46 --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_report_definitions_command.json @@ -0,0 +1,14 @@ +{ + "test_case_1": { + "args": {"manageable": "true"}, + "mock_response": {"error_code":0,"error_msg":"","response":{"manageable": [ + {"description": "Test","id":"2","modifiedTime":"1673270090","name":"Critical and Exploitable Vulnerabilities Report","owner": + {"firstname":"test","id":"1","lastname":"","username":"test","uuid":"UUID_1"}, + "ownerGroup":{"description":"Full Access group","id":"0","name":"Full Access"},"type":"pdf"}, + {"description": "Test 2","id":"1","modifiedTime":"1673270079","name":"Critical and Exploitable Vulnerabilities Report","owner": + {"firstname":"test 1","id":"1","lastname":"","username":"test","uuid":"UUID_2"}, + "ownerGroup":{"description":"Full Access group","id":"0","name":"Full Access"},"type":"pdf"}]}, "timestamp":1684676253,"type":"regular","warnings":[]}, + "expected_hr": "### Tenable.sc Report Definitions\n|ID|Name|Description|Type|Group|Owner|\n|---|---|---|---|---|---|\n| 2 | Critical and Exploitable Vulnerabilities Report | Test | pdf | Full Access | test |\n", + "expected_ec": [{"ID": "2", "Name": "Critical and Exploitable Vulnerabilities Report", "Type": "pdf", "Group": "Full Access", "Owner": "test"}] + } +} \ No newline at end of file diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_users_command.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_users_command.json new file mode 100644 index 000000000000..8bdf427a230a --- /dev/null +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_users_command.json @@ -0,0 +1,45 @@ +{ + "test_case_1":{ + "args": { + "id": "1", + "username": "secman", + "email": "Test1@test.com" + }, + "mock_response": {"error_code":0,"error_msg":"","response":{"createdTime":"1675677287","email":"Test1@test.com","firstname":"","id":"1","lastLogin":"1684916014","lastname":"", + "modifiedTime":"1682935546","role":{"description":"Test","id":"1","name":"Security Manager"},"title":"","username":"secman1","uuid":"uuid_1"}, + "timestamp":1684916053,"type":"regular","warnings":[]}, + "expected_hr": "### Tenable.sc Users\n|ID|Username|Email|Created|Modified|LastLogin|Role|\n|---|---|---|---|---|---|---|\n| 1 | secman1 | Test1@test.com | 2023-02-06T09:54:47Z | 2023-05-01T10:05:46Z | 2023-05-24T08:13:34Z | Security Manager |\n", + "expected_ec": [{"ID": "1", "Username": "secman1", "Email": "Test1@test.com", "Created": "2023-02-06T09:54:47Z", "Modified": "2023-05-01T10:05:46Z", "LastLogin": "2023-05-24T08:13:34Z", "Role": "Security Manager"}] + }, + "test_case_3":{ + "args": { + "username": "secman1", + "email": "Test2@test.com" + }, + "mock_response": {"error_code":0,"error_msg":"","response":[{"createdTime":"1675677287","email":"Test1@test.com","firstname":"","id":"1","lastLogin":"1684916014","lastname":"", + "modifiedTime":"1682935546","role":{"description":"Test","id":"12","name":"Security Manager"},"title":"","username":"secman1","uuid":"uuid_1"}, + {"createdTime":"1675677287","email":"Test2@test.com","firstname":"","id":"2","lastLogin":"1684916014","lastname":"", + "modifiedTime":"1682935546","role":{"description":"Test","id":"2","name":"Security Manager"},"title":"","username":"secman2","uuid":"uuid_2"}, + {"createdTime":"1675677287","email":"Test3@test.com","firstname":"","id":"3","lastLogin":"1684916014","lastname":"", + "modifiedTime":"1682935546","role":{"description":"Test","id":"32","name":"Security Manager"},"title":"","username":"secman3","uuid":"uuid_3"}], + "timestamp":1684916053,"type":"regular","warnings":[]}, + + "expected_hr": "### Tenable.sc Users\n|ID|Username|Email|Created|Modified|LastLogin|Role|\n|---|---|---|---|---|---|---|\n| 1 | secman1 | Test1@test.com | 2023-02-06T09:54:47Z | 2023-05-01T10:05:46Z | 2023-05-24T08:13:34Z | Security Manager |\n", + "expected_ec": [{"ID": "1", "Username": "secman1", "Email": "Test1@test.com", "Created": "2023-02-06T09:54:47Z", "Modified": "2023-05-01T10:05:46Z", "LastLogin": "2023-05-24T08:13:34Z", "Role": "Security Manager"}] + }, + "test_case_2":{ + "args": { + }, + "mock_response": {"error_code":0,"error_msg":"","response":[{"createdTime":"1675677287","email":"Test1@test.com","firstname":"","id":"1","lastLogin":"1684916014","lastname":"", + "modifiedTime":"1682935546","role":{"description":"Test","id":"12","name":"Security Manager"},"title":"","username":"secman1","uuid":"uuid_1"}, + {"createdTime":"1675677287","email":"Test2@test.com","firstname":"","id":"2","lastLogin":"1684916014","lastname":"", + "modifiedTime":"1682935546","role":{"description":"Test","id":"2","name":"Security Manager"},"title":"","username":"secman2","uuid":"uuid_2"}, + {"createdTime":"1675677287","email":"Test3@test.com","firstname":"","id":"3","lastLogin":"1684916014","lastname":"", + "modifiedTime":"1682935546","role":{"description":"Test","id":"32","name":"Security Manager"},"title":"","username":"secman3","uuid":"uuid_3"}], + "timestamp":1684916053,"type":"regular","warnings":[]}, + "expected_hr": "### Tenable.sc Users\n|ID|Username|Email|Created|Modified|LastLogin|Role|\n|---|---|---|---|---|---|---|\n| 1 | secman1 | Test1@test.com | 2023-02-06T09:54:47Z | 2023-05-01T10:05:46Z | 2023-05-24T08:13:34Z | Security Manager |\n| 2 | secman2 | Test2@test.com | 2023-02-06T09:54:47Z | 2023-05-01T10:05:46Z | 2023-05-24T08:13:34Z | Security Manager |\n| 3 | secman3 | Test3@test.com | 2023-02-06T09:54:47Z | 2023-05-01T10:05:46Z | 2023-05-24T08:13:34Z | Security Manager |\n", + "expected_ec": [{"ID": "1", "Username": "secman1", "Email": "Test1@test.com", "Created": "2023-02-06T09:54:47Z", "Modified": "2023-05-01T10:05:46Z", "LastLogin": "2023-05-24T08:13:34Z", "Role": "Security Manager"}, + {"ID": "2", "Username": "secman2", "Email": "Test2@test.com", "Created": "2023-02-06T09:54:47Z", "Modified": "2023-05-01T10:05:46Z", "LastLogin": "2023-05-24T08:13:34Z", "Role": "Security Manager"}, + {"ID": "3", "Username": "secman3", "Email": "Test3@test.com", "Created": "2023-02-06T09:54:47Z", "Modified": "2023-05-01T10:05:46Z", "LastLogin": "2023-05-24T08:13:34Z", "Role": "Security Manager"}] + } +} \ No newline at end of file From 4e36c5256f47624cb9e817d5783ba495bc21e247 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Wed, 24 May 2023 14:07:21 +0300 Subject: [PATCH 041/115] changes --- .../Integrations/Tenable_sc/README.md | 98 +++++++++++++- .../Integrations/Tenable_sc/Tenable_sc.py | 25 ++-- .../Integrations/Tenable_sc/Tenable_sc.yml | 4 +- .../Tenable_sc/Tenable_sc_test.py | 126 +++++++++++++++++- .../test_data/test_list_queries.json | 1 - 5 files changed, 227 insertions(+), 27 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/README.md b/Packs/Tenable_sc/Integrations/Tenable_sc/README.md index e83702cb79a3..922372df06e9 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/README.md +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/README.md @@ -64,6 +64,10 @@ Requires security manager authentication. Get a list of Tenable.sc existing scan #### Human Readable Output +### Tenable.sc Scans +|ID|Name|Description|Policy|Group|Owner| +|---|---|---|---|---|---| +| 3 | test_scan_2023 | Test scan | Network Scan | Full Access | secman | ### tenable-sc-launch-scan @@ -146,6 +150,32 @@ Requires security manager authentication. Get details about a given vulnerabilit #### Human Readable Output +## Vulnerability: FTP Server Detection (10092) +### Synopsis +An FTP server is listening on a remote port. +### Description +It is possible to obtain the banner of the remote FTP server by connecting to a remote port. +### Solution + +### Hosts +|IP|MAC|Port|Protocol| +|---|---|---|---| +| | | 21 | TCP | +### Risk Information +|RiskFactor| +|---| +| None | +### Exploit Information +|ExploitAvailable| +|---| +| false | +### Plugin Details +|CheckType|Family|Modified|Published| +|---|---|---|---| +| remote | Service detection | 2019-11-22T17:00:00Z | 1999-10-12T16:00:00Z | +### Vulnerability Information +**No entries.** + ### tenable-sc-get-scan-status *** @@ -362,14 +392,14 @@ There are no input arguments for this command. |---|---|---| | 2 | RHEL6 Scanner | 1 | -### Requires security manager authentication. tenable-sc-create-scan +### tenable-sc-create-scan *** -Create a scan on Tenable.sc +Requires security manager role. Create a scan on Tenable.sc #### Base Command -`Requires security manager authentication. tenable-sc-create-scan` +`tenable-sc-create-scan` #### Input @@ -413,6 +443,8 @@ Create a scan on Tenable.sc #### Human Readable Output +Scan successfully deleted + ### tenable-sc-delete-scan *** @@ -543,6 +575,9 @@ Requires security manager authentication. Delete the Asset with the given ID fro There is no context output for this command. #### Human Readable Output + +Asset successfully deleted + ### tenable-sc-list-alerts *** @@ -896,6 +931,8 @@ This command can be executed with both authentication types (admin or security m #### Human Readable Output + + ### tenable-sc-update-user *** @@ -987,6 +1024,8 @@ update user details by given user_id. #### Human Readable Output + + ### tenable-sc-delete-user *** @@ -1002,11 +1041,14 @@ This command can be executed with both authentication types (admin or security m | --- | --- | --- | | user_id | The id of the user we want to delete. | Required | +#### Human Readable Output + +User is deleted. + #### Context Output There is no context output for this command. -#### Human Readable Output ### tenable-sc-list-plugin-family *** @@ -1036,6 +1078,19 @@ Requires security manager authentication. list plugin families / return informat #### Human Readable Output +When plugin_id isn't given: +### Plugin families: +|Plugin ID|Plugin Name| +|---|---| +| 0 | N/A | +| 1 | Red Hat Local Security Checks | + +When plugin_id is given: +### Plugin families: +|Plugin ID|Plugin Name|Is Active| +|---|---|---| +| 2 | HP-UX Local Security Checks | true | + ### tenable-sc-create-policy *** @@ -1110,6 +1165,11 @@ Requires security manager authentication. This command is prerequisite for creat #### Human Readable Output +### Policy was created successfully: +|Policy type|name|Created Time|Plugin Families|Policy Status|Policy UUID|Policy can Manage|Creator Username|policyTemplate Name| +|---|---|---|---|---|---|---|---|---| +| regular | scan_name | 1684923394 | {'id': '1', 'name': 'Red Hat Local Security Checks', 'count': '9297', 'plugins': []} | 0 | | true | yuv | Advanced Scan | + ### tenable-sc-list-query *** @@ -1248,6 +1308,19 @@ Requires security manager authentication. Lists queries. #### Human Readable Output +If query_id isn't given: +### Queries: +|Query Id|Query Name|Query Description|Query Filters|Query Manageable|Query Usable| +|---|---|---|---|---|---| +| 1 | test_name | test_description | filter | True | True | +| 2 | test_name | test_description | | True | False | + +If query_id is given: +### Query +|Query Id|Query Name|Query Description| +|---|---|---| +| test_id | test_name | test_description | + ### tenable-sc-update-asset *** @@ -1399,6 +1472,11 @@ Requires security manager authentication. This command is prerequisite for creat #### Human Readable Output +### Remediation scan created successfully +|Scan ID|Scan Name|Scan Type|Dhcp Tracking status|Created Time|Modified Time|Max Scan Time|Policy id |Policy context|Schedule type|Group|Owner| +|---|---|---|---|---|---|---|---|---|---|---|---| +| 69 | my_Test_scan | policy | false | 2023-05-24T10:12:27Z | 1684923147 | 3600 | 1000044 | scan | now | Full Access | yuv | + ### tenable-sc-launch-scan-report *** @@ -1443,9 +1521,15 @@ Requires security manager authentication. Polling command. Launch a scan by give | TenableSC.ScanResults.ImportTime | date | Scan import time | #### Human Readable Output - -#### Human Readable Output - +### Tenable.sc Scan 130 Report +|ID|Name|Description|Policy|Group|Owner|ScannedIPs|StartTime|EndTime|Duration|Checks|ImportTime|RepositoryName|Status|Scan Type|Completed IPs| +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 130 | test_scan_2023 | Test scan 2023 | Network Scan | Full Access | hayun_test_sec_man | 156 | 2023-05-16T12:18:10Z | 2023-05-16T17:20:00Z | 301.8333333333333 | 22649640 | 2023-05-16T17:20:02Z | Local | Completed | regular | 156 | +### Vulnerabilities +|ID|Name|Family|Severity|Total| +|---|---|---|---|---| +| 10092 | FTP Server Detection | Service detection | Info | 6 | +| 10107 | HTTP Server Type and Version | Web Servers | Info | 61 | ## Troubleshooting For errors within Tenable.sc, the cause is generally specified, e.g., The currently logged in used is not an administrator, Unable to retrieve Asset #2412. Asset #2412 does not exist or Invalid login credentials. However there might be connection errors, for example when the server URL provided is incorrect. \ No newline at end of file diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 8febf704480c..9efe2dc51f85 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -30,13 +30,10 @@ def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str password: str = "", access_key: str = "", secret_key: str = "", url: str = ""): if not proxy: - try: - del os.environ['HTTP_PROXY'] - del os.environ['HTTPS_PROXY'] - del os.environ['http_proxy'] - del os.environ['https_proxy'] - except Exception as e: - demisto.debug(f"Encountered the following error: {e}") + del os.environ['HTTP_PROXY'] + del os.environ['HTTPS_PROXY'] + del os.environ['http_proxy'] + del os.environ['https_proxy'] self.url = f"{get_server_url(url)}/rest" self.verify_ssl = verify_ssl @@ -1354,7 +1351,7 @@ def delete_asset_command(client: Client, args: Dict[str, Any]): def list_report_definitions_command(client: Client, args: Dict[str, Any]): """ - Lists report defenitions. + Lists report definitions. Args: client (Client): The tenable.sc client object. args (Dict): demisto.args() object. @@ -1471,9 +1468,9 @@ def get_elements(elements, manageable): List: The desired extracted list. """ if manageable == 'false': - return elements.get('usable') + return elements.get('usable', []) - return elements.get('manageable') + return elements.get('manageable', []) def create_scan_command(client: Client, args: Dict[str, Any]): @@ -1629,12 +1626,12 @@ def launch_scan(client: Client, args: Dict[str, Any]): target_password = args.get('diagnostic_password') if (target_address and not target_password) or (target_password and not target_address): - raise DemistoException('Error: If a target is provided, both IP/Hostname and the password must be provided') + raise DemistoException("Error: If one of diagnostic target or password is provided, both of them must be provided.") res = client.launch_scan(scan_id, {'address': target_address, 'password': target_password}) if not res or 'response' not in res or not res['response'] or 'scanResult' not in res['response']: - raise DemistoException('Error: Could not retrieve the scan') + raise DemistoException('Error: Could not retrieve the scan.') return res @@ -2261,7 +2258,7 @@ def get_alert_command(client: Client, args: Dict[str, Any]): raise DemistoException('Alert not found') alert = res['response'] - query_res = client.get_query(alert['query'].get('id')) + query_res = client.get_query(alert.get('query', {}).get('id')) query = query_res.get('response') alert_headers = ['ID', 'Name', 'Description', 'LastTriggered', 'State', 'Behavior', 'Actions'] @@ -2877,6 +2874,8 @@ def main(): # pragma: no cover secret_key=secret_key, url=url ) + print(client._base_url) + print(client._verify) if command == 'fetch-incidents': first_fetch = params.get('fetch_time').strip() fetch_incidents(client, first_fetch) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index e9b6ff9879ae..9927efea765d 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -714,9 +714,9 @@ script: secret: false defaultValue: 'true' deprecated: false - description: Create a scan on Tenable.sc + description: Requires security manager role. Create a scan on Tenable.sc execution: false - name: Requires security manager role. tenable-sc-create-scan + name: tenable-sc-create-scan outputs: - contextPath: TenableSC.Scan.ID description: Scan ID. diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py index be8de436c90c..d9e03a6a4cb1 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py @@ -1,6 +1,9 @@ import pytest import json -from Tenable_sc import * +from Tenable_sc import update_asset_command, list_zones_command, list_queries, create_policy_request_body, list_groups_command, \ + list_plugin_family_command, validate_create_scan_inputs, Client, validate_user_body_params, get_query, \ + create_get_device_request_params_and_path, create_user_request_body, list_query_command, list_users_command, launch_scan, \ + list_report_definitions_command import io client_mocker = Client(verify_ssl=False, proxy=True, access_key="access_key", secret_key="secret_key", @@ -229,7 +232,7 @@ def test_validate_credentials(test_case): password = test_data.get('password') with pytest.raises(Exception) as e: - Client(access_key=access_key, secret_key=secret_key, user_name=user_name, password=password) + Client(proxy=True, access_key=access_key, secret_key=secret_key, user_name=user_name, password=password) assert "Please provide either user_name and password or secret_key and access_key" in str(e.value) @@ -352,7 +355,7 @@ def test_get_query(mocker, test_case): Given: - test case that point to the relevant test case in the json test data which include: query_id, response mock, expected hr, and expected_ec. - - Case 1: response mock that misses filters section + - Case 1: response mock that misses filters section. When: - Running get_query. @@ -383,7 +386,7 @@ def test_list_queries(mocker, test_case): Then: - Ensure that the response was parsed correctly and right HR and EC is returned. - - Case 1: Should Include the filters section in the HR, + - Case 1: Should Include the filters section in the HR, should have "True" in both manageable and usable columns for the mutual query and fill False in the none-mutual queries. """ test_data = load_json("./test_data/test_list_queries.json").get(test_case, {}) @@ -391,3 +394,118 @@ def test_list_queries(mocker, test_case): _, hr, query = list_queries(client_mocker, "") assert test_data.get('expected_hr') == hr assert test_data.get('expected_ec') == query + + +@pytest.mark.parametrize("test_case", ["test_case_1", "test_case_2"]) +def test_list_query_command(mocker, test_case): + """ + Given: + - test case that point to the relevant test case in the json test data which include: + args, function to mock, response mock, expected hr, and expected_ec. + - Case 1: empty args, mocked_function = list_queries, response mock with 2 manageable and 2 usable queries, + one of the queries appears in both lists, some queries with filters and some don't. + - Case 2: args with query_id, mocked_function = get_query, response mock that misses filters section. + + When: + - Running list_query_command. + + Then: + - Ensure that the right function is mocked and the response was parsed correctly and right HR and EC is returned. + - Case 1: Should call list_queries, Include the filters section in the HR, + should have "True" in both manageable and usable columns for the mutual query and fill False in the none-mutual queries. + - Case 2: Should call get_query, exclude the filters section from the HR and EC. + """ + test_data = load_json("./test_data/test_list_query_command.json").get(test_case, {}) + mocked_function = test_data.get("mocked_function") + args = test_data.get("args") + mocker.patch(f"Tenable_sc.Client.{mocked_function}", return_value=test_data.get('mock_response')) + command_results = list_query_command(client_mocker, args) + assert test_data.get('expected_hr') == command_results.readable_output + assert test_data.get('expected_ec') == command_results.outputs + + +@pytest.mark.parametrize("test_case", ["test_case_1", "test_case_2", "test_case_3"]) +def test_list_users_command(mocker, test_case): + """ + Given: + - test case that point to the relevant test case in the json test data which include: + args, response mock, expected hr, and expected_ec. + - Case 1: args with id and email pointing to an email different from the email in the response, + response of a get user request. + - Case 2: args with email and username, response of a list users request with 3 users. + - Case 2: Empty args, response of a list users request with 3 users. + + When: + - Running list_users_command. + + Then: + - Ensure that the response was parsed correctly and right HR and EC is returned. + - Case 1: Should include the retrieved user in the response. + - Case 2: Should filter out the user with un matched username and ignore the email argument. + - Case 2: Should retrieve HR and EC including all 3 users. + """ + test_data = load_json("./test_data/test_list_users_command.json").get(test_case, {}) + args = test_data.get("args") + mocker.patch.object(client_mocker, 'send_request', return_value=test_data.get('mock_response')) + command_results = list_users_command(client_mocker, args) + assert test_data.get('expected_hr') == command_results.readable_output + assert test_data.get('expected_ec') == command_results.outputs + + +@pytest.mark.parametrize("test_case", ["test_case_1", "test_case_2", "test_case_3", "test_case_4", "test_case_5"]) +def test_launch_scan_errors(mocker, test_case): + """ + Given: + - test case that point to the relevant test case in the json test data which include: + args, command_type flag and the expected error message. + - Case 1: Args with diagnostic_target but no diagnostic_password. + - Case 2: Args with diagnostic_password but no diagnostic_target. + - Case 3: Args with both diagnostic_password and diagnostic_target, and an empty mock response. + - Case 4: Args without both diagnostic_password and diagnostic_target, + and an empty mock response with empty response section. + - Case 5: Args with both diagnostic_password and diagnostic_target, + and a response with missing scanResult from response section. + + When: + - Running launch_scan. + + Then: + - Ensure that the right error was thrown. + - Case 1: Should throw an error for none-number argument. + - Case 2: Should throw an error for invalid time_zone. + - Case 3: Should throw aCould not retrieve the scans error. + - Case 4: Should throw aCould not retrieve the scans error. + - Case 5: Should throw aCould not retrieve the scans error. + """ + test_data = load_json("./test_data/test_launch_scan_errors.json").get(test_case, {}) + args = test_data.get('args') + mocker.patch.object(client_mocker, 'send_request', return_value=test_data.get('mock_response', {})) + + with pytest.raises(Exception) as e: + launch_scan(client_mocker, args) + + assert test_data.get('expected_error_msg') in str(e.value) + + +@pytest.mark.parametrize("test_case", ["test_case_1"]) +def test_list_report_definitions_command(mocker, test_case): + """ + Given: + - test case that point to the relevant test case in the json test data which include: + args, response mock, expected hr, and expected_ec. + - Case 1: args with manageable = true, and mock response to response with 2 report definitions with the same name, + where one report is later than the second. + + When: + - Running list_report_definitions_command. + + Then: + - Ensure that the response was parsed correctly and right HR and EC is returned. + - Case 1: Should return only one report definition, the one that occurred later. + """ + test_data = load_json("./test_data/test_list_report_definitions_command.json").get(test_case, {}) + args = test_data.get("args") + mocker.patch.object(client_mocker, 'send_request', return_value=test_data.get('mock_response')) + command_results = list_report_definitions_command(client_mocker, args) + assert test_data.get('expected_hr') == command_results.readable_output + assert test_data.get('expected_ec') == command_results.outputs diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_queries.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_queries.json index 59122b872358..2468c33bb083 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_queries.json +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_queries.json @@ -1,6 +1,5 @@ { "test_case_1": { - "query_id": "test_id", "mock_response": {"error_code":0,"error_msg":"", "response": { "manageable": [ From 9f237038430c15b3b2ce3b58b153fb7adad69290 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Wed, 24 May 2023 15:58:27 +0300 Subject: [PATCH 042/115] updated readme --- .../Integrations/Tenable_sc/README.md | 140 +++++++++++- .../Integrations/Tenable_sc/Tenable_sc.py | 211 ++++++++---------- .../Integrations/Tenable_sc/Tenable_sc.yml | 2 +- .../Tenable_sc/Tenable_sc_test.py | 9 +- ...create_get_vulnerability_request_body.json | 8 - 5 files changed, 229 insertions(+), 141 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/README.md b/Packs/Tenable_sc/Integrations/Tenable_sc/README.md index 922372df06e9..f614219b3266 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/README.md +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/README.md @@ -98,6 +98,11 @@ Requires security manager authentication. Launch an existing scan from Tenable.s #### Human Readable Output +### Tenable.sc Scan +|Name|ID|OwnerID|JobID|Status| +|---|---|---|---|---| +| test_scan_2023 | 169 | 38 | 118864 | Queued | + ### tenable-sc-get-vulnerability *** @@ -202,6 +207,11 @@ Requires security manager authentication. Get the status of a specific scan in T #### Human Readable Output +### Tenable.sc Scan Status +|ID|Name|Status|Description| +|---|---|---|---| +| 169 | test_scan_2023 | Running | Test scan 2023 | + ### tenable-sc-get-scan-report *** @@ -244,6 +254,15 @@ Requires security manager authentication. Get a single report with Tenable.sc sc #### Human Readable Output +### Tenable.sc Scan 150 Report +|ID|Name|Policy|Group|Owner|ScannedIPs|StartTime|EndTime|Duration|Checks|ImportTime|RepositoryName|Status|Scan Type|Completed IPs| +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 150 | my_Test_scan | Plugin #1 | Full Access | yuv | 115 | 2023-05-18T13:12:51Z | 2023-05-18T13:45:53Z | 33.03333333333333 | 21275 | 2023-05-18T13:45:57Z | Local | Completed | regular | 115 | +### Vulnerabilities +|ID|Name|Family|Severity|Total| +|---|---|---|---|---| +| 11219 | Nessus SYN scanner | Port scanners | Info | 109 | + ### tenable-sc-list-credentials *** @@ -274,6 +293,13 @@ Requires security manager authentication. Get a list of Tenable.sc credentials. #### Human Readable Output +### Tenable.sc Credentials +|ID|Name|Type|Group|LastModified| +|---|---|---|---|---| +| 1 | Windows server | windows | | 2023-02-14T11:44:12Z | +| 2 | SSH linux | ssh | | 2023-02-15T09:11:10Z | +| 3 | Windows clients | windows | | 2023-02-15T12:32:45Z | + ### tenable-sc-list-policies *** @@ -304,6 +330,12 @@ Requires security manager authentication. Get a list of Tenable.sc scan policies #### Human Readable Output +### Tenable.sc Scan Policies +|ID|Name|Description|Type|Group|Owner|LastModified| +|---|---|---|---|---|---|---| +| 1 | Network Scan | | Basic Network Scan | | | 2023-02-09T14:58:26Z | +| 2 | D Advanced Scan | D Advanced Scan | Advanced Scan | | | 2023-02-13T13:02:22Z | + ### tenable-sc-list-report-definitions *** @@ -332,6 +364,11 @@ Requires security manager authentication. Get a list of Tenable.sc report defini #### Human Readable Output +### Tenable.sc Report Definitions +|ID|Name|Description|Type|Group|Owner| +|---|---|---|---|---|---| +| 2 | Critical and Exploitable Vulnerabilities Report | Test | pdf | Full Access | test | + ### tenable-sc-list-repositories *** @@ -355,6 +392,11 @@ There are no input arguments for this command. #### Human Readable Output +### Tenable.sc Scan Repositories +|ID|Name| +|---|---| +| 1 | Local | + ### tenable-sc-list-zones *** @@ -388,7 +430,8 @@ There are no input arguments for this command. |ID|Name|IPList|activeScanners| |---|---|---|---| | 1 | Default Scan Zone | ip | 1 | -### Tenable.sc Scanners\n|ID|Name|Status| +### Tenable.sc Scanners +|ID|Name|Status| |---|---|---| | 2 | RHEL6 Scanner | 1 | @@ -443,7 +486,10 @@ Requires security manager role. Create a scan on Tenable.sc #### Human Readable Output -Scan successfully deleted +### Scan created successfully +|ID|CreatorID|Name|Type|CreationTime| +|---|---|---|---|---| +| 70 | 39 | my_name | policy | 2023-05-24T12:33:03Z | ### tenable-sc-delete-scan @@ -465,6 +511,9 @@ Requires security manager authentication. Delete a scan in Tenable.sc There is no context output for this command. #### Human Readable Output + +Scan successfully deleted + ### tenable-sc-list-assets *** @@ -495,6 +544,13 @@ Requires security manager authentication. Get a list of Tenable.sc Assets. #### Human Readable Output +### Tenable.sc Assets +|ID|Name|Tag|Owner|Type|HostCount|LastModified| +|---|---|---|---|---|---|---| +| 0 | All Defined Ranges | | | static | 0 | 2023-01-09T13:13:52Z | +| 1 | asset_1_name | | test | dynamic | 106 | 2023-05-21T09:12:52Z | +| 2 | Systems that have been Scanned | | test | dynamic | 152 | 2023-01-09T13:14:43Z | + ### tenable-sc-create-asset *** @@ -525,6 +581,11 @@ Requires security manager authentication. Create an Asset in Tenable.sc with pro #### Human Readable Output +### Asset created successfully +|ID|Name|OwnerName| +|---|---|---| +| 42 | example output | yuv | + ### tenable-sc-get-asset *** @@ -555,6 +616,11 @@ Requires security manager authentication. Get details for a given asset in Tenab #### Human Readable Output +### Tenable.sc Asset +|ID|Name|Description|Created|Modified|Owner|Group|IPs| +|---|---|---|---|---|---|---|---| +| 1 | asset_1_name | asset_1_description | 2023-01-09T13:14:43Z | 2023-05-21T09:12:52Z | test | Full Access | | + ### tenable-sc-delete-asset *** @@ -609,6 +675,12 @@ Requires security manager authentication. List alerts from Tenable.sc. #### Human Readable Output +### Tenable.sc Alerts +|ID|Name|Actions|State|LastTriggered|LastEvaluated|Group|Owner| +|---|---|---|---|---|---|---|---| +| 1 | Test Alert 1 | ticket | Triggered | 2023-02-16T07:13:08Z | 2023-05-23T13:30:01Z | Full Access | lmanager | +| 2 | Test Alert 2 | scan | Triggered | 2023-02-16T07:14:07Z | 2023-05-24T12:14:08Z | Full Access | lmanager | + ### tenable-sc-get-alert *** @@ -642,6 +714,19 @@ Requires security manager authentication. Get information about a given alert in #### Human Readable Output +### Tenable.sc Alert +ID|Name|LastTriggered|State|Behavior| +|---|---|---|---|---| +| 1 | Test Alert 1 | 2023-02-16T07:13:08Z | Triggered | Execute only on first trigger | +### Condition +|Trigger|Query| +|---|---| +| sumip \u003e= 10 | Query for alert 'Test Alert 1' at 1676531587 | +### Actions +|Type|Values| +|---|---| +| ticket | lmanager | + ### tenable-sc-get-device *** @@ -685,6 +770,11 @@ Requires security manager authentication. Gets the specified device information. #### Human Readable Output +### Tenable.sc Device +|IP| UUID | MacAddress| +|---|---|---| +| | | | + ### tenable-sc-list-users *** @@ -719,6 +809,12 @@ Results may vary based on the authentication type (admin or security manager). L #### Human Readable Output +### Tenable.sc Users +|ID|Username|Title|Email|Created|Modified|LastLogin|Role| +|---|---|---|---|---|---|---|---| +| 1 | test | | | 2023-01-09T13:13:53Z | 2023-05-24T10:23:29Z | | Security Manager | +| 2 | secman | | | 2023-02-06T09:54:47Z | 2023-05-01T10:05:46Z | 2023-05-24T12:43:35Z | Security Manager | + ### tenable-sc-get-system-licensing *** @@ -742,6 +838,11 @@ There are no input arguments for this command. #### Human Readable Output +### Tenable.sc Licensing information +|License|LicensedIPS|ActiveIPS| +|---|---|---| +| Valid | 512 | 152 | + ### tenable-sc-get-system-information *** @@ -771,6 +872,11 @@ There are no input arguments for this command. #### Human Readable Output +### Tenable.sc System information +|RPMStatus|JavaStatus|DiskStatus|DiskThreshold|LastCheck| +|---|---|---|---|---| +| true | true | true | 5% | 2023-05-24T04:10:02Z | + ### tenable-sc-get-all-scan-results *** @@ -809,6 +915,13 @@ Requires security manager authentication. Returns all scan results in Tenable.sc #### Human Readable Output +### Tenable.sc Scan results - 0-1 +Total number of elements is 77 +|ID|Name|Status|Description|Policy|Group|Owner|ScannedIPs|StartTime|EndTime|Duration|Checks|ImportTime|RepositoryName| +|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| 92 | test_scan_2023-mart-05-1950 | Error | Test scan 2023 | Network Scan | Full Access | secman | 0 | 2023-04-24T23:50:07Z | 2023-04-25T01:10:13Z | 80.1 | 22639720 | | Local | +| 93 | test_scan_2023-mart-05-1950 | Error | Test scan 2023 | Network Scan | Full Access | secman | 0 | 2023-04-25T23:50:07Z | 2023-04-26T00:30:44Z | 40.61666666666667 | 12624659 | | Local | + ### tenable-sc-list-groups *** @@ -840,6 +953,19 @@ Requires security manager authentication. list all groups. #### Human Readable Output +## Tenable.sc groups +|ID| +|---| +| 0 | +### Group id:0 +|Username|Firstname|Lastname| +|---|---|---| +| test | test | | +| secman | | | +| testuser1 | fname | lname | +| testuser444 | fname2 | lname2 | +| testuser3 | fname3 | lname3 | + ### tenable-sc-create-user *** @@ -931,7 +1057,10 @@ This command can be executed with both authentication types (admin or security m #### Human Readable Output - +### User example_output was created successfully. +|User type|User Id|User Status|User Name|User Role Name|User Group Name| +|---|---|---|---|---|---| +| regular | 57 | 0 | example_output | Security Analyst | Full Access | ### tenable-sc-update-user @@ -1024,7 +1153,10 @@ update user details by given user_id. #### Human Readable Output - +### user 23 was updated successfully. +|User type|User Id|User Status|User Name|First Name|Lat Name |Email |User Role Name|User Group Name| +|---|---|---|---|---|---|---|---|---| +| regular | 23 | 0 | testuser30 | testuser30 | testuser30 | testuser30@mymail.com | Credential Manager | Full Access | ### tenable-sc-delete-user diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 9efe2dc51f85..6c1ffad31f9c 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -23,6 +23,7 @@ 'report': 'report.name', 'ticket': 'assignee.username' } +FIELDS_TO_INCLUDE = 'id,name,description,type,ownerGroup,owner,tags,modifiedTime' class Client(BaseClient): @@ -30,10 +31,13 @@ def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str password: str = "", access_key: str = "", secret_key: str = "", url: str = ""): if not proxy: - del os.environ['HTTP_PROXY'] - del os.environ['HTTPS_PROXY'] - del os.environ['http_proxy'] - del os.environ['https_proxy'] + try: + del os.environ['HTTP_PROXY'] + del os.environ['HTTPS_PROXY'] + del os.environ['http_proxy'] + del os.environ['https_proxy'] + except Exception as e: + demisto.debug(f"encountered the following issue: {e}") self.url = f"{get_server_url(url)}/rest" self.verify_ssl = verify_ssl @@ -47,7 +51,7 @@ def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str if secret_key and access_key: self.headers['x-apikey'] = f"accesskey={access_key}; secretkey={secret_key}" BaseClient.__init__(self, base_url=self.url, headers=self.headers, verify=verify_ssl, proxy=proxy) - self.send_request = self.send_request_new + self.send_request = self.send_request_api_key_auth else: self.session = Session() integration_context = demisto.getIntegrationContext() @@ -55,11 +59,11 @@ def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str self.cookie = integration_context.get('cookie') self.user_name = user_name self.password = password - self.send_request = self.send_request_old + self.send_request = self.send_request_username_and_password_auth if not self.token or not self.cookie: self.login() - def send_request_new(self, path, method='get', body={}, params={}, headers=None): + def send_request_api_key_auth(self, path, method='GET', body={}, params={}, headers=None): """ Send the requests for access & secret keys authentication method. Args: @@ -74,7 +78,7 @@ def send_request_new(self, path, method='get', body={}, params={}, headers=None) headers = headers or self.headers return self._http_request(method, url_suffix=path, params=params, data=json.dumps(body), headers=headers) - def send_request_old(self, path, method='get', body=None, params=None, headers=None, try_number=1): + def send_request_username_and_password_auth(self, path, method='GET', body=None, params=None, headers=None, try_number=1): """ Send the requests for username & password authentication method. Args: @@ -172,7 +176,7 @@ def logout(self): """ Send the request to logout for username & password authentication method. """ - self.send_request(path='token', method='delete') + self.send_request(path='token', method='DELETE') def create_scan(self, args: Dict[str, Any]): """ @@ -184,7 +188,7 @@ def create_scan(self, args: Dict[str, Any]): """ body = self.create_scan_body(args) - return self.send_request(path='scan', method='post', body=body) + return self.send_request(path='scan', method='POST', body=body) def create_scan_body(self, args): """ @@ -238,12 +242,12 @@ def create_scan_body(self, args): body['maxScanTime'] = max_scan_time * 3600 if schedule := args.get('schedule'): - body['schedule'] = { + schedule_body = { 'type': schedule } if dependent := args.get('dependent_id'): - body['schedule']['dependentID'] = dependent + schedule_body['dependentID'] = dependent if schedule == 'ical': start_time = args.get("start_time") @@ -258,18 +262,19 @@ def create_scan_body(self, args): except Exception: start_time = parse_date_range(start_time, date_format=timestamp_format)[0] if time_zone := args.get("time_zone") and start_time: - body['schedule']['start'] = f"TZID={time_zone}:{start_time}" + schedule_body['start'] = f"TZID={time_zone}:{start_time}" else: raise DemistoException("Please make sure to provide both time_zone and start_time.") if all([repeat_rule_freq, repeat_rule_interval, repeat_rule_by_day]): - body['schedule']['repeatRule'] = f"FREQ={repeat_rule_freq};INTERVAL={repeat_rule_interval};" + schedule_body['repeatRule'] = f"FREQ={repeat_rule_freq};INTERVAL={repeat_rule_interval};" f"BYDAY={repeat_rule_by_day}" elif repeat_rule_freq and repeat_rule_interval: - body['schedule']['repeatRule'] = f"FREQ={repeat_rule_freq};INTERVAL={repeat_rule_interval}" + schedule_body['repeatRule'] = f"FREQ={repeat_rule_freq};INTERVAL={repeat_rule_interval}" elif any([repeat_rule_freq, repeat_rule_interval, repeat_rule_by_day]): raise DemistoException("Please make sure to provide repeat_rule_freq, repeat_rule_interval with or without " "repeat_rule_by_day, or don't provide any of them.") - body['schedule']['enabled'] = argToBoolean(args.get("enabled", True)) + schedule_body['enabled'] = argToBoolean(args.get("enabled", True)) + body['schedule'] = schedule_body remove_nulls_from_dictionary(body) return body @@ -433,20 +438,15 @@ def get_assets(self, fields): return self.send_request(path='asset', params=params) - def get_credentials(self, fields): + def get_credentials(self): """ Send the request for list_credentials_command. - Args: - fields (str): The fields to include in the response. Returns: Dict: The response. """ - params = None - - if fields: - params = { - 'fields': fields - } + params = { + 'fields': FIELDS_TO_INCLUDE + } return self.send_request(path='credential', params=params) @@ -491,7 +491,7 @@ def create_asset(self, name, description, owner_id, tags, ips): if tags: body['tags'] = tags - return self.send_request(path='asset', method='post', body=body) + return self.send_request(path='asset', method='POST', body=body) def delete_asset(self, asset_id): """ @@ -501,7 +501,7 @@ def delete_asset(self, asset_id): Returns: Dict: The response. """ - return self.send_request(path=f'asset/{asset_id}', method='delete') + return self.send_request(path=f'asset/{asset_id}', method='DELETE') def get_report_definitions(self, fields): """ @@ -557,13 +557,13 @@ def create_query(self, scan_id, tool): path = 'query' body = { - 'name': 'scan ' + scan_id + ' query', + 'name': f'scan {scan_id} query', 'type': 'vuln', 'tool': tool, 'scanID': scan_id } - return self.send_request(path, method='post', body=body) + return self.send_request(path, method='POST', body=body) def delete_query(self, query_id): """ @@ -576,45 +576,34 @@ def delete_query(self, query_id): if not query_id: raise DemistoException('query id returned None') path = 'query/' + str(query_id) - self.send_request(path, method='delete') + self.send_request(path, method='DELETE') - def get_analysis(self, query=None, scan_results_id=None, args={}, calling_command="get_vulnerability_command"): + def get_analysis(self, body=None, args={}): """ Send the request for get_vulnerability_command and get_vulnerabilities. Args: - query (dict or str): This function can receive query argument either as a dict (as in get_vulnerability_command), - or as an ID of an existing query (as in get_vulnerabilities). - scan_results_id (None or str): str if received from get_vulnerabilities, otherwise will be part of args. args (dict): Either an empty dict if passed from get_vulnerabilities, otherwise, the demisto.results() object. - calling_command (str): The command that call this function. + body (str): The request body (if function is called from get_vulnerabilities). Returns: Dict: The response. """ - body = self.create_get_vulnerability_request_body(args, query, scan_results_id) + body = body or self.create_get_vulnerability_request_body(args) - return self.send_request(path='analysis', method='post', body=body) + return self.send_request(path='analysis', method='POST', body=body) - def create_get_vulnerability_request_body(self, args={}, query=None, scan_results_id=None, - calling_command="get_vulnerability_command"): + def create_get_vulnerability_request_body(self, args={}): """ Create the body for the request made in get_analysis. Args: - query (dict or str): This function can receive query argument either as a dict (as in get_vulnerability_command), - or as an ID of an existing query (as in get_vulnerabilities). - scan_results_id (None or str): str if received from get_vulnerabilities, otherwise will be part of args. args (dict): Either an empty dict if passed from get_vulnerabilities, otherwise, the demisto.results() object. - calling_command (str): The command that call this function. Returns: Dict: The prepared request body. """ vuln_id = args.get('vulnerability_id') - scan_results_id = scan_results_id or args.get('scan_results_id') + scan_results_id = args.get('scan_results_id') sort_field = args.get('sort_field', 'severity') - query_id = query or args.get('query_id') - if not isinstance(query_id, dict): - query = {'id': query_id} - else: - query = query_id + query_id = args.get('query_id') + query = {'id': query_id} sort_direction = args.get('sort_direction', "ASC") source_type = args.get('source_type', "individual") page = int(args.get('page', '0')) @@ -625,29 +614,25 @@ def create_get_vulnerability_request_body(self, args={}, query=None, scan_result 'type': 'vuln', 'view': 'all', 'sourceType': source_type, + 'startOffset': page, # Lower bound for the results list (must be specified) + 'endOffset': page + limit, # Upper bound for the results list (must be specified) + 'sortField': sort_field, + 'sortDir': sort_direction, + 'tool': 'vulndetails', } - if calling_command == "get_vulnerability_command": - body.update({ - 'startOffset': page, # Lower bound for the results list (must be specified) - 'endOffset': page + limit, # Upper bound for the results list (must be specified) - 'sortField': sort_field, - 'sortDir': sort_direction, - 'tool': 'vulndetails', - }) if source_type == 'individual': if scan_results_id: body['scanID'] = scan_results_id else: raise DemistoException("When choosing source_type = individual - scan_results_id must be provided.") - if calling_command == "get_vulnerability_command": - vuln_filter = [{ - 'filterName': 'pluginID', - 'operator': '=', - 'value': vuln_id - }] - query["filters"] = vuln_filter - query["tool"] = 'vulndetails' - query["type"] = 'vuln' + vuln_filter = [{ + 'filterName': 'pluginID', + 'operator': '=', + 'value': vuln_id + }] + query["filters"] = vuln_filter + query["tool"] = 'vulndetails' + query["type"] = 'vuln' else: body['sourceType'] = source_type if not query_id: @@ -684,7 +669,7 @@ def list_groups(self, show_users): if show_users: params['fields'] = 'users' - return self.send_request(path='group', method='get', params=params) + return self.send_request(path='group', method='GET', params=params) def get_vulnerability(self, vuln_id): """ @@ -711,7 +696,7 @@ def delete_scan(self, scan_id): Returns: Dict: The response. """ - return self.send_request(path=f'scan/{scan_id}', method='delete') + return self.send_request(path=f'scan/{scan_id}', method='DELETE') def get_device(self, uuid, ip, dns_name, repo): """ @@ -991,7 +976,7 @@ def validate_user_body_params(args: Dict[str, Any], command_type: str): raise DemistoException("Password length must be at least 3 characters.") if email and not re.compile(emailRegex).match(email): - raise DemistoException(f"Error: The given email address: {email} is not valid") + raise DemistoException(f"Error: The given email address: {email} is not in the correct format.") if command_type == 'create' and not email_notice == 'none' and not email: raise DemistoException("When email_notice is different from none, an email must be given as well.") @@ -1041,7 +1026,7 @@ def list_scans_command(client: Client, args: Dict[str, Any]): res = client.get_scans('id,name,description,policy,ownerGroup,owner') manageable = args.get('manageable', 'false').lower() - if not res or 'response' not in res or not res['response']: + if not (res and res.get('response')): raise DemistoException('No scans found') scans_dicts = get_elements(res['response'], manageable) @@ -1082,7 +1067,7 @@ def list_policies_command(client: Client, args: Dict[str, Any]): manageable = args.get('manageable', 'false').lower() - if not res or 'response' not in res or not res['response']: + if not (res and res.get('response')): raise DemistoException('No policies found') policies = get_elements(res['response'], manageable) @@ -1123,7 +1108,7 @@ def list_repositories_command(client: Client, args: Dict[str, Any]): """ res = client.get_repositories() - if not res or 'response' not in res or not res['response']: + if not (res and res.get('response')): raise DemistoException('No repositories found') repositories = res['response'] @@ -1157,11 +1142,11 @@ def list_credentials_command(client: Client, args: Dict[str, Any]): Returns: CommandResults: command results object with the response, human readable section, and the context entries to add. """ - res = client.get_credentials('id,name,description,type,ownerGroup,owner,tags,modifiedTime') + res = client.get_credentials() manageable = args.get('manageable', 'false').lower() - if not res or 'response' not in res or not res['response']: + if not (res and res.get('response')): raise DemistoException('No credentials found') credentials = get_elements(res['response'], manageable) @@ -1204,7 +1189,7 @@ def list_assets_command(client: Client, args: Dict[str, Any]): manageable = args.get('manageable', 'false').lower() - if not res or 'response' not in res or not res['response']: + if not (res and res.get('response')): raise DemistoException('No assets found') assets = get_elements(res['response'], manageable) @@ -1362,7 +1347,7 @@ def list_report_definitions_command(client: Client, args: Dict[str, Any]): manageable = args.get('manageable', 'false').lower() - if not res or 'response' not in res or not res['response']: + if not (res and res.get('response')): raise DemistoException('No report definitions found') reports = get_elements(res['response'], manageable) @@ -1680,7 +1665,7 @@ def get_scan_status(client: Client, args: Dict[str, Any]): scans_results = [] for scan_results_id in scan_results_ids: res = client.get_scan_results(scan_results_id) - if not res or 'response' not in res or not res['response']: + if not (res and res.get('response')): raise DemistoException('Scan results not found') scans_results.append(res['response']) @@ -1701,7 +1686,7 @@ def get_scan_report_command(client: Client, args: Dict[str, Any]): res = client.get_scan_report(scan_results_id) - if not res or 'response' not in res or not res['response']: + if not (res and res.get('response')): raise DemistoException('Scan results not found') scan_results = res['response'] @@ -1767,8 +1752,15 @@ def get_vulnerabilities(client: Client, scan_results_id): if not query or 'response' not in query: return 'Could not get vulnerabilites query' - analysis = client.get_analysis(query=query['response']['id'], scan_results_id=scan_results_id, - calling_command="get_vulnerabilities") + body = { + 'type': 'vuln', + 'view': 'all', + 'sourceType': 'individual', + 'scanID': scan_results_id, + 'query': {"id": query.get('response', {}).get('id')} + } + + analysis = client.get_analysis(body=body) client.delete_query(query.get('response', {}).get('id')) @@ -1980,21 +1972,8 @@ def get_device_command(client: Client, args: Dict[str, Any]): device = res['response'] headers = [ - 'IP', - 'UUID', - 'MacAddress', - 'RepositoryID', - 'RepositoryName', - 'NetbiosName', - 'DNSName', - 'OS', - 'OsCPE', - 'LastScan', - 'TotalScore', - 'LowSeverity', - 'MediumSeverity', - 'HighSeverity', - 'CriticalSeverity' + 'IP', 'UUID', 'MacAddress', 'RepositoryID', 'RepositoryName', 'NetbiosName', 'DNSName', 'OS', 'OsCPE', 'LastScan', + 'TotalScore', 'LowSeverity', 'MediumSeverity', 'HighSeverity', 'CriticalSeverity' ] mapped_device = { @@ -2007,7 +1986,7 @@ def get_device_command(client: Client, args: Dict[str, Any]): 'DNSName': device.get('dnsName'), 'OS': re.sub('<[^<]+?>', ' ', device['os']).lstrip() if device.get('os') else '', 'OsCPE': device.get('osCPE'), - 'LastScan': timestamp_to_utc(device['lastScan']), + 'LastScan': timestamp_to_utc(device.get('lastScan')), 'TotalScore': device.get('total'), 'LowSeverity': device.get('severityLow'), 'MediumSeverity': device.get('severityMedium'), @@ -2168,27 +2147,19 @@ def get_system_information_command(client: Client, args: Dict[str, Any]): system = sys_res['response'] mapped_information = { - 'Version': system['version'], - 'BuildID': system['buildID'], - 'ReleaseID': system['releaseID'], - 'License': system['licenseStatus'], - 'RPMStatus': diagnostics['statusRPM'], - 'JavaStatus': diagnostics['statusJava'], - 'DiskStatus': diagnostics['statusDisk'], - 'DiskThreshold': diagnostics['statusThresholdDisk'], - 'LastCheck': timestamp_to_utc(diagnostics['statusLastChecked']), + 'Version': system.get('version'), + 'BuildID': system.get('buildID'), + 'ReleaseID': system.get('releaseID'), + 'License': system.get('licenseStatus'), + 'RPMStatus': diagnostics.get('statusRPM'), + 'JavaStatus': diagnostics.get('statusJava'), + 'DiskStatus': diagnostics.get('statusDisk'), + 'DiskThreshold': diagnostics.get('statusThresholdDisk'), + 'LastCheck': timestamp_to_utc(diagnostics.get('statusLastChecked')), } headers = [ - 'Version', - 'BuildID', - 'ReleaseID', - 'License', - 'RPMStatus', - 'JavaStatus', - 'DiskStatus', - 'DiskThreshold', - 'LastCheck' + 'Version', 'BuildID', 'ReleaseID', 'License', 'RPMStatus', 'JavaStatus', 'DiskStatus', 'DiskThreshold', 'LastCheck' ] return CommandResults( @@ -2213,7 +2184,7 @@ def list_alerts_command(client: Client, args: Dict[str, Any]): 'action,lastEvaluated,ownerGroup,owner') manageable = args.get('manageable', 'false').lower() - if not res or 'response' not in res or not res['response']: + if not (res and res.get('response')): raise DemistoException('No alerts found') alerts = get_elements(res['response'], manageable) @@ -2254,7 +2225,7 @@ def get_alert_command(client: Client, args: Dict[str, Any]): alert_id = args.get('alert_id') res = client.get_alerts(alert_id=alert_id) - if not res or 'response' not in res or not res['response']: + if not (res and res.get('response')): raise DemistoException('Alert not found') alert = res['response'] @@ -2418,7 +2389,7 @@ def get_all_scan_results_command(client: Client, args: Dict[str, Any]): if limit > 200: limit = 200 - if not res or 'response' not in res or not res['response']: + if not (res and res.get('response')): raise DemistoException('Scan results not found') elements = get_elements(res['response'], get_manageable_results) @@ -2468,7 +2439,7 @@ def create_user_command(client: Client, args: Dict[str, Any]): validate_user_body_params(args, "create") res = client.create_user(args) hr_header = f'User {args.get("user_name")} was created successfully.' - process_update_and_create_user_response(res, hr_header) + return process_update_and_create_user_response(res, hr_header) def update_user_command(client: Client, args: Dict[str, Any]): @@ -2484,7 +2455,7 @@ def update_user_command(client: Client, args: Dict[str, Any]): validate_user_body_params(args, "update") res = client.update_user(args, user_id) hr_header = f'user {args.get("user_id")} was updated successfully.' - process_update_and_create_user_response(res, hr_header) + return process_update_and_create_user_response(res, hr_header) def process_update_and_create_user_response(res, hr_header): @@ -2874,8 +2845,6 @@ def main(): # pragma: no cover secret_key=secret_key, url=url ) - print(client._base_url) - print(client._verify) if command == 'fetch-incidents': first_fetch = params.get('fetch_time').strip() fetch_incidents(client, first_fetch) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index 9927efea765d..ac8da6b2e8f7 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -678,7 +678,7 @@ script: required: false secret: false - default: false - description: to specify repeating events based on an interval of a repeat_rule_freq or more. + description: To specify repeating events based on an interval of a repeat_rule_freq or more. isArray: false name: repeat_rule_freq required: false diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py index d9e03a6a4cb1..4f153f63483e 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py @@ -329,7 +329,6 @@ def test_create_get_vulnerability_request_body(mocker, test_case): Empty query, scan_results_id, and calling_command. - Case 2: Args with scan_results_id, vulnerability_id, query_id, source_type and limit higher than 200. Empty query, scan_results_id, and calling_command. - - Case 3: Empty args, filled query and scan_results_id, and calling_command = get_vulnerabilities. When: - Running create_get_vulnerability_request_body. @@ -338,14 +337,10 @@ def test_create_get_vulnerability_request_body(mocker, test_case): - Ensure that the body was created correctly. - Case 1: Should complete all the none-given fields, count source_type as individual, and add related fields. - Case 2: Should lower limit to 200, ignore scan_results_id and vulnerability_id. - - Case 3: Shouldn't handle any filtering fields, only create the basic body. """ test_data = load_json("./test_data/test_create_get_vulnerability_request_body.json").get(test_case, {}) - args = test_data.get('args') - query = test_data.get('query') - scan_results_id = test_data.get('scan_results_id') - calling_command = test_data.get("calling_command", "get_vulnerability_command") - body = client_mocker.create_get_vulnerability_request_body(args, query, scan_results_id, calling_command) + args = test_data.get('args', {}) + body = client_mocker.create_get_vulnerability_request_body(args) assert test_data.get('expected_body') == body diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_get_vulnerability_request_body.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_get_vulnerability_request_body.json index 6a1fc967155a..15dbbd00136d 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_get_vulnerability_request_body.json +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_create_get_vulnerability_request_body.json @@ -24,13 +24,5 @@ "query": {}, "expected_body": {"tool": "vulndetails", "type": "vuln", "startOffset": 0, "endOffset": 200, "sortField": "severity", "sortDir": "ASC", "sourceType": "patched", "view": "all", "query": {"id": "1"}} - }, - "test_case_3":{ - "args": { - }, - "scan_results_id": "scan_results_id", - "query": {"query_id": "1"}, - "calling_command": "get_vulnerabilities", - "expected_body": {"type": "vuln", "view": "all", "sourceType": "individual", "scanID": "scan_results_id", "query": {"query_id": "1"}} } } \ No newline at end of file From 592809f9fee12f696f15c187a551b820a0999a74 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Wed, 24 May 2023 18:28:50 +0300 Subject: [PATCH 043/115] cr and validation fixes --- Packs/Tenable_sc/.secrets-ignore | 3 +- .../Integrations/Tenable_sc/Tenable_sc.py | 49 +++++++++---------- .../Tenable_sc/Tenable_sc_test.py | 6 +-- .../test_validate_user_body_params.json | 2 +- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Packs/Tenable_sc/.secrets-ignore b/Packs/Tenable_sc/.secrets-ignore index 463a2fe55ae2..07f4f90047fd 100644 --- a/Packs/Tenable_sc/.secrets-ignore +++ b/Packs/Tenable_sc/.secrets-ignore @@ -9,4 +9,5 @@ e1C::c https://access.redhat.com 213.35.2.109 https://user-images.githubusercontent.com -https://docs.oracle.com \ No newline at end of file +https://docs.oracle.com +testuser30@mymail.com \ No newline at end of file diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 6c1ffad31f9c..f7c02390f7bf 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -24,9 +24,11 @@ 'ticket': 'assignee.username' } FIELDS_TO_INCLUDE = 'id,name,description,type,ownerGroup,owner,tags,modifiedTime' +API_KEY = "API_KEY" +USERNAME_AND_PASSWORD = "USERNAME_AND_PASSWORD" -class Client(BaseClient): +class Client(BaseClient, object): def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str = "", password: str = "", access_key: str = "", secret_key: str = "", url: str = ""): @@ -46,6 +48,7 @@ def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str 'Accept': 'application/json', 'Content-Type': 'application/json' } + self.auth_method = API_KEY if not (user_name and password) and not (secret_key and access_key): raise DemistoException("Please provide either user_name and password or secret_key and access_key") if secret_key and access_key: @@ -60,9 +63,13 @@ def __init__(self, verify_ssl: bool = True, proxy: bool = False, user_name: str self.user_name = user_name self.password = password self.send_request = self.send_request_username_and_password_auth + self.auth_method = USERNAME_AND_PASSWORD if not self.token or not self.cookie: self.login() + def __enter__(self): + return self + def send_request_api_key_auth(self, path, method='GET', body={}, params={}, headers=None): """ Send the requests for access & secret keys authentication method. @@ -106,7 +113,7 @@ def send_request_username_and_password_auth(self, path, method='GET', body=None, if res.status_code == 403 and try_number <= self.max_retries: self.login() headers['X-SecurityCenter'] = self.token # The Token is being updated in the login - return self.send_request_old(path, method, body, params, headers, try_number + 1) + return self.send_request_username_and_password_auth(path, method, body, params, headers, try_number + 1) elif res.status_code < 200 or res.status_code >= 300: try: @@ -172,11 +179,12 @@ def send_login_request(self, login_body): return res.json() - def logout(self): + def __exit__(self, *args): """ Send the request to logout for username & password authentication method. """ - self.send_request(path='token', method='DELETE') + if self.auth_method == USERNAME_AND_PASSWORD: + self.send_request(path='token', method='DELETE') def create_scan(self, args: Dict[str, Any]): """ @@ -928,7 +936,8 @@ def create_user_request_body(args: Dict[str, Any]): def get_server_url(url): """ - Retrieve the server url. + Remove redundant '/' from the url the server url. + For example: www.example.com/ - > www.example.com. Args: url (str): The server url. Returns: @@ -2834,31 +2843,21 @@ def main(): # pragma: no cover 'tenable-sc-get-device': get_device_command, 'tenable-sc-create-remediation-scan': create_remediation_scan_command } - + try: - client = Client( - verify_ssl=verify_ssl, - proxy=proxy, - user_name=user_name, - password=password, - access_key=access_key, - secret_key=secret_key, - url=url - ) - if command == 'fetch-incidents': - first_fetch = params.get('fetch_time').strip() - fetch_incidents(client, first_fetch) - elif command == 'tenable-sc-launch-scan-report': - return_results(launch_scan_report_command(args, client)) - else: - return_results(command_dict[command](client, args)) + with Client(verify_ssl=verify_ssl, proxy=proxy, user_name=user_name, password=password, access_key=access_key, + secret_key=secret_key, url=url) as client: + if command == 'fetch-incidents': + first_fetch = params.get('fetch_time').strip() + fetch_incidents(client, first_fetch) + elif command == 'tenable-sc-launch-scan-report': + return_results(launch_scan_report_command(args, client)) + else: + return_results(command_dict[command](client, args)) except Exception as e: return_error( f'Failed to execute {command} command. Error: {str(e)}' ) - finally: - if (user_name and password) and not (access_key and secret_key): - client.logout() if __name__ in ('__main__', '__builtin__', 'builtins'): diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py index 4f153f63483e..3afdc4ba7b8f 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py @@ -3,7 +3,7 @@ from Tenable_sc import update_asset_command, list_zones_command, list_queries, create_policy_request_body, list_groups_command, \ list_plugin_family_command, validate_create_scan_inputs, Client, validate_user_body_params, get_query, \ create_get_device_request_params_and_path, create_user_request_body, list_query_command, list_users_command, launch_scan, \ - list_report_definitions_command + list_report_definitions_command, get_server_url import io client_mocker = Client(verify_ssl=False, proxy=True, access_key="access_key", secret_key="secret_key", @@ -319,8 +319,8 @@ def test_create_get_device_request_params_and_path(test_case): assert test_data.get('expected_params') == params -@pytest.mark.parametrize("test_case", ["test_case_1", "test_case_2", "test_case_3"]) -def test_create_get_vulnerability_request_body(mocker, test_case): +@pytest.mark.parametrize("test_case", ["test_case_1", "test_case_2"]) +def test_create_get_vulnerability_request_body(test_case): """ Given: - test case that point to the relevant test case in the json test data which include: diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_user_body_params.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_user_body_params.json index a5d2182428e1..fa2894728017 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_user_body_params.json +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_validate_user_body_params.json @@ -22,7 +22,7 @@ "test_case_5":{ "args": {"email": "email"}, "command_type": "create", - "expected_error_msg": "Error: The given email address: email is not valid" + "expected_error_msg": "Error: The given email address: email is not in the correct format." }, "test_case_6":{ "args": {"email_notice": "both"}, From 7e43772ce7f61148da77c9c073cdebb7280c2623 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Wed, 24 May 2023 18:57:51 +0300 Subject: [PATCH 044/115] validation fixes --- Packs/Tenable_sc/Integrations/Tenable_sc/README.md | 14 +++++++------- .../Integrations/Tenable_sc/Tenable_sc.py | 2 +- .../Integrations/Tenable_sc/Tenable_sc_test.py | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/README.md b/Packs/Tenable_sc/Integrations/Tenable_sc/README.md index f614219b3266..f81a3f7ef2e1 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/README.md +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/README.md @@ -165,7 +165,7 @@ It is possible to obtain the banner of the remote FTP server by connecting to a ### Hosts |IP|MAC|Port|Protocol| |---|---|---|---| -| | | 21 | TCP | +| {IP} | {MAC} | 21 | TCP | ### Risk Information |RiskFactor| |---| @@ -619,7 +619,7 @@ Requires security manager authentication. Get details for a given asset in Tenab ### Tenable.sc Asset |ID|Name|Description|Created|Modified|Owner|Group|IPs| |---|---|---|---|---|---|---|---| -| 1 | asset_1_name | asset_1_description | 2023-01-09T13:14:43Z | 2023-05-21T09:12:52Z | test | Full Access | | +| 1 | asset_1_name | asset_1_description | 2023-01-09T13:14:43Z | 2023-05-21T09:12:52Z | test | Full Access | {IPs_list} | ### tenable-sc-delete-asset @@ -773,7 +773,7 @@ Requires security manager authentication. Gets the specified device information. ### Tenable.sc Device |IP| UUID | MacAddress| |---|---|---| -| | | | +| {IP} | {UUID} | {MacAddress} | ### tenable-sc-list-users @@ -1175,7 +1175,7 @@ This command can be executed with both authentication types (admin or security m #### Human Readable Output -User is deleted. +User {user_id} is deleted. #### Context Output @@ -1300,7 +1300,7 @@ Requires security manager authentication. This command is prerequisite for creat ### Policy was created successfully: |Policy type|name|Created Time|Plugin Families|Policy Status|Policy UUID|Policy can Manage|Creator Username|policyTemplate Name| |---|---|---|---|---|---|---|---|---| -| regular | scan_name | 1684923394 | {'id': '1', 'name': 'Red Hat Local Security Checks', 'count': '9297', 'plugins': []} | 0 | | true | yuv | Advanced Scan | +| regular | scan_name | 1684923394 | {'id': '1', 'name': 'Red Hat Local Security Checks', 'count': '9297', 'plugins': []} | 0 | {policy UUID} | true | yuv | Advanced Scan | ### tenable-sc-list-query @@ -1448,7 +1448,7 @@ If query_id isn't given: | 2 | test_name | test_description | | True | False | If query_id is given: -### Query +### Query {query_id} |Query Id|Query Name|Query Description| |---|---|---| | test_id | test_name | test_description | @@ -1479,7 +1479,7 @@ There is no context output for this command. #### Human Readable Output -asset was updated successfully. +asset {asset_id} was updated successfully. ### tenable-sc-create-remediation-scan diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index f7c02390f7bf..135bf2e1a1a1 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -2843,7 +2843,7 @@ def main(): # pragma: no cover 'tenable-sc-get-device': get_device_command, 'tenable-sc-create-remediation-scan': create_remediation_scan_command } - + try: with Client(verify_ssl=verify_ssl, proxy=proxy, user_name=user_name, password=password, access_key=access_key, secret_key=secret_key, url=url) as client: diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py index 3afdc4ba7b8f..11f96793966f 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc_test.py @@ -3,7 +3,7 @@ from Tenable_sc import update_asset_command, list_zones_command, list_queries, create_policy_request_body, list_groups_command, \ list_plugin_family_command, validate_create_scan_inputs, Client, validate_user_body_params, get_query, \ create_get_device_request_params_and_path, create_user_request_body, list_query_command, list_users_command, launch_scan, \ - list_report_definitions_command, get_server_url + list_report_definitions_command import io client_mocker = Client(verify_ssl=False, proxy=True, access_key="access_key", secret_key="secret_key", From 4394cb4a846f6784d81bf51bf244db087d4676a0 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Sun, 28 May 2023 16:46:58 +0300 Subject: [PATCH 045/115] added tpb --- .../Integrations/Tenable_sc/README.md | 2 + .../Integrations/Tenable_sc/Tenable_sc.py | 27 +- .../Integrations/Tenable_sc/Tenable_sc.yml | 9 + .../playbook-tenable.sc_scan_test.yml | 2 + .../playbook-tenable.sc_test.yml | 1172 +++++++++++++---- Tests/conf.json | 7 +- 6 files changed, 936 insertions(+), 283 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/README.md b/Packs/Tenable_sc/Integrations/Tenable_sc/README.md index f81a3f7ef2e1..36dd36d9fdab 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/README.md +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/README.md @@ -251,6 +251,8 @@ Requires security manager authentication. Get a single report with Tenable.sc sc | TenableSC.ScanResults.Owner | string | Scan owner user name. | | TenableSC.ScanResults.Duration | number | Scan duration in minutes. | | TenableSC.ScanResults.ImportTime | date | Scan import time. | +| TenableSC.ScanResults.IsScanRunning | boolean | Wether scan is still running or not. | +| TenableSC.ScanResults.ImportStatus | string | Scan import status. | #### Human Readable Output diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 135bf2e1a1a1..126a9491cf77 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -353,7 +353,7 @@ def get_all_scan_results(self): """ params = { 'fields': 'name,description,details,status,scannedIPs,startTime,scanDuration,importStart,' - 'finishTime,completedChecks,owner,ownerGroup,repository' + 'finishTime,completedChecks,owner,ownerGroup,repository,importStatus' } return self.send_request(path='scanResult', params=params) @@ -548,7 +548,7 @@ def get_scan_report(self, scan_results_id): params = { 'fields': 'name,description,details,status,scannedIPs,progress,startTime,scanDuration,importStart,' - 'finishTime,completedChecks,owner,ownerGroup,repository,policy' + 'finishTime,completedChecks,owner,ownerGroup,repository,policy,importStatus,running' } return self.send_request(path, params=params) @@ -1721,15 +1721,17 @@ def get_scan_report_command(client: Client, args: Dict[str, Any]): 'ScannedIPs': scan_results.get('scannedIPs', ''), 'Owner': scan_results.get('owner', {}).get('username', ''), 'RepositoryName': scan_results.get('repository', {}).get('name', ''), - 'Import Status': scan_results.get('importStatus', ''), - 'Is Scan Running ': scan_results.get('running', ''), - 'Completed IPs': scan_results.get('progress', {}).get('completedIPs', '') + 'ImportStatus': scan_results.get('importStatus', ''), + 'IsScanRunning': scan_results.get('running', '') } + if progress := scan_results.get('progress', {}): + mapped_results["Completed IPs"] = progress.get('completedIPs', '') + hr = tableToMarkdown('Tenable.sc Scan ' + mapped_results['ID'] + ' Report', mapped_results, headers, removeNull=True) - if len(vulnerabilities_to_get) > 0: + if len(vulnerabilities_to_get) > 0 and scan_results.get("importStatus", "") != "Error": vulns = get_vulnerabilities(client, scan_results_id) if isinstance(vulns, list): @@ -1856,7 +1858,7 @@ def get_vulnerability_command(client: Client, args: Dict[str, Any]): 'Name': vuln['name'], 'Description': vuln['description'], 'Type': vuln['type'], - 'Severity': vuln['severity'].get('name'), + 'Severity': vuln.get('severity', {}).get('name'), 'Synopsis': vuln['synopsis'], 'Solution': vuln['solution'] } @@ -2371,8 +2373,6 @@ def list_groups_command(client: Client, args: Dict[str, Any]): mapped_groups[index]['Users'] = users group_id = group.get('id') hr += f"{tableToMarkdown(f'Group id:{group_id}', users, headers, removeNull=True)}\n" - groups = mapped_groups - return CommandResults( outputs=createContext(response_to_context(groups), removeNull=True), outputs_prefix='TenableSC.Group', @@ -2420,7 +2420,8 @@ def get_all_scan_results_command(client: Client, args: Dict[str, Any]): 'ImportTime': timestamp_to_utc(elem['importStart']), 'ScannedIPs': elem['scannedIPs'], 'Owner': elem['owner'].get('username'), - 'RepositoryName': elem['repository'].get('name') + 'RepositoryName': elem['repository'].get('name'), + 'ImportStatus': elem.get('importStatus', '') } for elem in elements[page:page + limit]] readable_title = 'Tenable.sc Scan results - {0}-{1}'.format(page, page + limit - 1) @@ -2483,7 +2484,7 @@ def process_update_and_create_user_response(res, hr_header): response = res.get("response", {}) mapped_response = { "User type": res.get("type"), - "User Id": response.get("id"), + "User ID": response.get("id"), "User Status": response.get("status"), "User Name": response.get("username"), "First Name": response.get("firstname"), @@ -2517,7 +2518,7 @@ def delete_user_command(client: Client, args: Dict[str, Any]): return CommandResults( raw_response=res, - readable_output="User {user_id} is deleted." + readable_output=f"User {user_id} is deleted." ) @@ -2589,7 +2590,7 @@ def create_policy_command(client: Client, args: Dict[str, Any]): return CommandResults( outputs=createContext(response_to_context(created_policy), removeNull=True), - outputs_prefix='TenableSC.Query', + outputs_prefix='TenableSC.ScanPolicy', raw_response=res, outputs_key_field='ID', readable_output=tableToMarkdown('Policy was created successfully:', mapped_created_policy, headers, removeNull=True) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml index ac8da6b2e8f7..7bbac16c69ff 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.yml @@ -372,6 +372,12 @@ script: - contextPath: TenableSC.ScanResults.ImportTime description: Scan import time. type: date + - contextPath: TenableSC.ScanResults.IsScanRunning + description: Wether scan is still running or not. + type: boolean + - contextPath: TenableSC.ScanResults.ImportStatus + description: Scan import status. + type: string - arguments: - auto: PREDEFINED default: false @@ -1229,6 +1235,9 @@ script: - contextPath: TenableSC.ScanResults.RepositoryName description: Scan repository name. type: string + - contextPath: TenableSC.ScanResults.ImportStatus + description: Scan import status. + type: string - arguments: - auto: PREDEFINED default: false diff --git a/Packs/Tenable_sc/TestPlaybooks/playbook-tenable.sc_scan_test.yml b/Packs/Tenable_sc/TestPlaybooks/playbook-tenable.sc_scan_test.yml index ba194d13e036..c1769652734f 100644 --- a/Packs/Tenable_sc/TestPlaybooks/playbook-tenable.sc_scan_test.yml +++ b/Packs/Tenable_sc/TestPlaybooks/playbook-tenable.sc_scan_test.yml @@ -1,6 +1,8 @@ id: tenable-sc-scan-test version: -1 name: Test tenable scan +deprecated: true +comment: Deprecated. Use tenable-sc-launch-scan-report command instead. starttaskid: "0" tasks: "0": diff --git a/Packs/Tenable_sc/TestPlaybooks/playbook-tenable.sc_test.yml b/Packs/Tenable_sc/TestPlaybooks/playbook-tenable.sc_test.yml index 174ee619a80a..d926fac3f30d 100644 --- a/Packs/Tenable_sc/TestPlaybooks/playbook-tenable.sc_test.yml +++ b/Packs/Tenable_sc/TestPlaybooks/playbook-tenable.sc_test.yml @@ -1,15 +1,16 @@ id: tenable-sc-test version: -1 +vcShouldKeepItemLegacyProdMachine: false name: Tenable.sc Test description: Test playbook for Tenable.sc integration starttaskid: "0" tasks: "0": id: "0" - taskid: 0b14d100-9b12-4dcf-883e-abaf290abb6b + taskid: 6d7b6e63-a98f-40b0-806f-2daeac03ea07 type: start task: - id: 0b14d100-9b12-4dcf-883e-abaf290abb6b + id: 6d7b6e63-a98f-40b0-806f-2daeac03ea07 version: -1 name: "" iscommand: false @@ -18,22 +19,27 @@ tasks: '#none#': - "11" separatecontext: false + continueonerrortype: "" view: |- { "position": { - "x": 50, - "y": 50 + "x": 40, + "y": -750 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "1": id: "1" - taskid: 3c8f30ed-0f96-4046-8e46-1076e457960e + taskid: a4d1f78a-45b0-4151-887d-11ee8cace454 type: title task: - id: 3c8f30ed-0f96-4046-8e46-1076e457960e + id: a4d1f78a-45b0-4151-887d-11ee8cace454 version: -1 name: Create a scan and delete it type: title @@ -43,22 +49,27 @@ tasks: '#none#': - "2" separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 1040 + "y": 860 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "2": id: "2" - taskid: d08058fd-fba5-46ca-879c-071c54236e94 + taskid: de1e8381-b99b-4e36-86d1-3572e54b9130 type: regular task: - id: d08058fd-fba5-46ca-879c-071c54236e94 + id: de1e8381-b99b-4e36-86d1-3572e54b9130 version: -1 name: Get manageable policies description: Get a list of Tenable.sc scan policies @@ -73,22 +84,27 @@ tasks: manageable: simple: "true" separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 1185 + "y": 1005 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "3": id: "3" - taskid: 07a442ad-d44c-415b-8fa8-da47691d362e + taskid: 80d3b7f9-1dae-4490-8391-208698aef545 type: regular task: - id: 07a442ad-d44c-415b-8fa8-da47691d362e + id: 80d3b7f9-1dae-4490-8391-208698aef545 version: -1 name: Get repositories description: Get a list of Tenable.sc scan repositories @@ -100,22 +116,27 @@ tasks: '#none#': - "4" separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 1360 + "y": 1180 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "4": id: "4" - taskid: 44e47ec4-1efd-4c86-8dcb-5adedf49a1a2 + taskid: b6256c8f-4c4f-406c-885a-3939e5d193d2 type: regular task: - id: 44e47ec4-1efd-4c86-8dcb-5adedf49a1a2 + id: b6256c8f-4c4f-406c-885a-3939e5d193d2 version: -1 name: Get zones description: Get a list of Tenable.sc scan zones @@ -127,22 +148,27 @@ tasks: '#none#': - "5" separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 1535 + "y": 1355 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "5": id: "5" - taskid: b229268d-c93b-4e35-837d-c0ee5066ef1c + taskid: a4a40261-4d0d-4f33-845d-de51c22891f5 type: regular task: - id: b229268d-c93b-4e35-837d-c0ee5066ef1c + id: a4a40261-4d0d-4f33-845d-de51c22891f5 version: -1 name: Get credentials description: Get a list of Tenable.sc credentials @@ -153,25 +179,28 @@ tasks: nexttasks: '#none#': - "6" - scriptarguments: - manageable: {} separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 1710 + "y": 1530 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "6": id: "6" - taskid: d0ad26c8-45d7-49fb-8f24-205d2bf7757c + taskid: 3099831c-7aac-4f81-8872-96e0bf5a9f52 type: regular task: - id: d0ad26c8-45d7-49fb-8f24-205d2bf7757c + id: 3099831c-7aac-4f81-8872-96e0bf5a9f52 version: -1 name: Get current time for name description: | @@ -183,30 +212,28 @@ tasks: nexttasks: '#none#': - "8" - scriptarguments: - contextKey: {} - date: {} - dateFormat: {} - hoursAgo: {} - minutesAgo: {} - monthsAgo: {} separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 1885 + "y": 1705 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "7": id: "7" - taskid: 44cadbfe-ab32-4055-8211-d8ee00a20d8d + taskid: 4d6291a6-22e0-4a26-84dc-495180342be9 type: regular task: - id: 44cadbfe-ab32-4055-8211-d8ee00a20d8d + id: 4d6291a6-22e0-4a26-84dc-495180342be9 version: -1 name: Create scan description: Create a scan on Tenable.sc @@ -236,7 +263,6 @@ tasks: getField: value: simple: ID - dependent_id: {} description: simple: Test scan dhcp_tracking: @@ -278,26 +304,12 @@ tasks: simple: ID repository_id: complex: - root: TenableSC - accessor: ScanRepository - transformers: - - operator: WhereFieldEquals - args: - equalTo: - value: - simple: repo - field: - value: - simple: Name - getField: - value: - simple: ID - rollover_type: {} + root: TenableSC.ScanRepository + accessor: ID scan_virtual_hosts: simple: "false" schedule: simple: never - timeout_action: {} zone_id: complex: root: TenableSC @@ -312,22 +324,27 @@ tasks: simple: "0" accessor: ScanZone.ID separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 2235 + "y": 2055 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "8": id: "8" - taskid: 147bec15-2ee3-41bf-87a5-1c8d59416f80 + taskid: d5779f57-ed71-4f9a-862f-ec2e5b496e93 type: regular task: - id: 147bec15-2ee3-41bf-87a5-1c8d59416f80 + id: d5779f57-ed71-4f9a-862f-ec2e5b496e93 version: -1 name: Get manageable report definitions description: Get a list of Tenable.sc report definitions @@ -342,22 +359,27 @@ tasks: manageable: simple: "true" separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 2060 + "y": 1880 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "9": id: "9" - taskid: de65b08e-3b48-4949-8c6b-a6775bda66f7 + taskid: e165b8b5-48a0-4618-83f6-4bc1c50ae577 type: regular task: - id: de65b08e-3b48-4949-8c6b-a6775bda66f7 + id: e165b8b5-48a0-4618-83f6-4bc1c50ae577 version: -1 name: Delete the scan description: Delete a scan in Tenable.sc @@ -367,27 +389,32 @@ tasks: brand: Tenable.sc nexttasks: '#none#': - - "40" + - "46" scriptarguments: scan_id: simple: ${TenableSC.Scan.ID} separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 2410 + "y": 2240 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "10": id: "10" - taskid: a98ac779-301a-41ec-86cf-422411461075 + taskid: 62e3f64a-e853-4c5e-88f2-55553c1ce0b1 type: title task: - id: a98ac779-301a-41ec-86cf-422411461075 + id: 62e3f64a-e853-4c5e-88f2-55553c1ce0b1 version: -1 name: Launch scan type: title @@ -397,22 +424,27 @@ tasks: '#none#': - "13" separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 3110 + "y": 3300 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "11": id: "11" - taskid: 47e71a20-a9cb-475d-8b76-a7f2cbb1d8c4 + taskid: 02c5425c-9fbf-4268-89b8-92e7e10dc1a9 type: regular task: - id: 47e71a20-a9cb-475d-8b76-a7f2cbb1d8c4 + id: 02c5425c-9fbf-4268-89b8-92e7e10dc1a9 version: -1 name: Delete Context description: Delete field from context @@ -426,27 +458,28 @@ tasks: scriptarguments: all: simple: "yes" - index: {} - key: {} - keysToKeep: {} - subplaybook: {} separatecontext: false + continueonerrortype: "" view: |- { "position": { - "x": 50, - "y": 195 + "x": 40, + "y": -620 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "12": id: "12" - taskid: 03f0c2b1-4758-4244-8e28-5e2a6fae9826 + taskid: c76704a7-622a-4f6f-8dfa-1d17b266073d type: regular task: - id: 03f0c2b1-4758-4244-8e28-5e2a6fae9826 + id: c76704a7-622a-4f6f-8dfa-1d17b266073d version: -1 name: Delete Context description: Delete field from context @@ -460,27 +493,28 @@ tasks: scriptarguments: all: simple: "yes" - index: {} - key: {} - keysToKeep: {} - subplaybook: {} separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 2935 + "y": 3125 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "13": id: "13" - taskid: a220c3d4-32b9-43c8-8525-40204f341b42 + taskid: 9b07246f-6ad8-4130-880f-5dca072829da type: regular task: - id: a220c3d4-32b9-43c8-8525-40204f341b42 + id: 9b07246f-6ad8-4130-880f-5dca072829da version: -1 name: List scans description: Get a list of Tenable.sc existing scans @@ -495,22 +529,27 @@ tasks: manageable: simple: "true" separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 3255 + "y": 3445 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "14": id: "14" - taskid: 3de16a42-65f7-4e41-86bc-4a8cdcaaf937 + taskid: d0b488c4-038b-4e0b-8c91-be17f8382bb1 type: regular task: - id: 3de16a42-65f7-4e41-86bc-4a8cdcaaf937 + id: d0b488c4-038b-4e0b-8c91-be17f8382bb1 version: -1 name: Launch my_test_scan description: Launch an existing scan from Tenable.sc @@ -522,41 +561,34 @@ tasks: '#none#': - "15" scriptarguments: - diagnostic_password: {} - diagnostic_target: {} scan_id: complex: - root: TenableSC - accessor: Scan + root: TenableSC.Scan + accessor: ID transformers: - - operator: WhereFieldEquals - args: - equalTo: - value: - simple: my_test_scan - field: - value: - simple: Name - getField: - value: - simple: ID + - operator: FirstArrayElement separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 3430 + "y": 3620 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "15": id: "15" - taskid: 00eade45-4bc5-46eb-8c5a-b5a0daba85b4 + taskid: c7ce702c-721f-4db4-8f45-b37f841e050d type: regular task: - id: 00eade45-4bc5-46eb-8c5a-b5a0daba85b4 + id: c7ce702c-721f-4db4-8f45-b37f841e050d version: -1 name: Get scan status description: Get the status of a specific scan in Tenable.sc @@ -571,22 +603,27 @@ tasks: scan_results_id: simple: ${TenableSC.ScanResults.ID} separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 3605 + "y": 3795 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "16": id: "16" - taskid: 4f9d0c3c-d033-40d5-8cea-e31770cb8192 + taskid: 9102fd37-5cd1-4b23-8aaa-8ff5804afc64 type: regular task: - id: 4f9d0c3c-d033-40d5-8cea-e31770cb8192 + id: 9102fd37-5cd1-4b23-8aaa-8ff5804afc64 version: -1 name: Delete Context description: Delete field from context @@ -600,27 +637,28 @@ tasks: scriptarguments: all: simple: "yes" - index: {} - key: {} - keysToKeep: {} - subplaybook: {} separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 3780 + "y": 3970 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "17": id: "17" - taskid: 5f23837f-f6a9-44e4-8aff-2f7749adf33f + taskid: 1e79e7b6-b6d1-433a-826d-d2b091f01b51 type: title task: - id: 5f23837f-f6a9-44e4-8aff-2f7749adf33f + id: 1e79e7b6-b6d1-433a-826d-d2b091f01b51 version: -1 name: Get scan results type: title @@ -630,22 +668,27 @@ tasks: '#none#': - "43" separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 3955 + "y": 4145 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "18": id: "18" - taskid: 80973b58-0bae-47ef-8bae-b8dea8452f3f + taskid: e4e0dc94-1c31-4b05-8828-6347323f69d9 type: regular task: - id: 80973b58-0bae-47ef-8bae-b8dea8452f3f + id: e4e0dc94-1c31-4b05-8828-6347323f69d9 version: -1 name: Get report from a complete scan description: Get a report with a Tenable.sc scan results @@ -659,42 +702,55 @@ tasks: scriptarguments: scan_results_id: complex: - root: TenableSC - accessor: ScanResults - transformers: - - operator: WhereFieldEquals - args: - equalTo: + root: TenableSC.ScanResults + filters: + - - operator: isEqualString + left: value: - simple: "7330" - field: + simple: TenableSC.ScanResults.Status + iscontext: true + right: value: - simple: ID - getField: + simple: Completed + - - operator: isNotEqualString + left: value: - simple: ID + simple: TenableSC.ScanResults.ImportStatus + iscontext: true + right: + value: + simple: Error + accessor: ID + transformers: + - operator: FirstArrayElement vulnerability_severity: simple: Medium,Low,Info separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 4275 + "y": 4465 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "19": id: "19" - taskid: e32002da-0b30-48c1-86a6-7c8c8b35109b + taskid: 01d006b5-3ec8-4f65-8a8f-909f32ae63c2 type: regular task: - id: e32002da-0b30-48c1-86a6-7c8c8b35109b + id: 01d006b5-3ec8-4f65-8a8f-909f32ae63c2 version: -1 name: Get scan vulnerability - description: 'Get details about a given vulnerability from a given Tenable.sc scan ' + description: 'Get details about a given vulnerability from a given Tenable.sc + scan ' script: Tenable.sc|||tenable-sc-get-vulnerability type: regular iscommand: true @@ -705,53 +761,55 @@ tasks: scriptarguments: scan_results_id: complex: - root: TenableSC - accessor: ScanResults - transformers: - - operator: WhereFieldEquals - args: - equalTo: + root: TenableSC.ScanResults + filters: + - - operator: isNotEqualString + left: value: - simple: "7330" - field: + simple: TenableSC.ScanResults.ImportStatus + iscontext: true + right: value: - simple: ID - getField: + simple: Error + - - operator: isEqualString + left: value: - simple: ID + simple: TenableSC.ScanResults.Status + iscontext: true + right: + value: + simple: Completed + accessor: ID + transformers: + - operator: FirstArrayElement vulnerability_id: complex: - root: TenableSC - accessor: ScanResults.Vulnerability + root: TenableSC.ScanResults.Vulnerability + accessor: ID transformers: - - operator: WhereFieldEquals - args: - equalTo: - value: - simple: "10539" - field: - value: - simple: ID - getField: - value: - simple: ID + - operator: FirstArrayElement separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 4450 + "y": 4640 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "20": id: "20" - taskid: 75042a50-8100-4da6-8c55-a8284d084472 + taskid: ba4dd94f-e7a3-4e67-8718-ee557e2ad738 type: condition task: - id: 75042a50-8100-4da6-8c55-a8284d084472 + id: ba4dd94f-e7a3-4e67-8718-ee557e2ad738 version: -1 name: Verify get vulnerability description: |- @@ -765,7 +823,7 @@ tasks: brand: "" nexttasks: "yes": - - "45" + - "21" separatecontext: false conditions: - label: "yes" @@ -774,25 +832,30 @@ tasks: left: value: complex: - root: TenableSC - accessor: ScanResults.Vulnerability.Hosts.Port + root: TenableSC.ScanResults + accessor: ID iscontext: true + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 4625 + "y": 4815 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "21": id: "21" - taskid: 5d6fa634-9f08-49a3-825e-0cebce4a0708 + taskid: 1cf17bb5-718d-4009-8c2b-362d3f84d06c type: regular task: - id: 5d6fa634-9f08-49a3-825e-0cebce4a0708 + id: 1cf17bb5-718d-4009-8c2b-362d3f84d06c version: -1 name: Delete Context description: Delete field from context @@ -806,11 +869,8 @@ tasks: scriptarguments: all: simple: "yes" - index: {} - key: {} - keysToKeep: {} - subplaybook: {} separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -821,12 +881,16 @@ tasks: note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "22": id: "22" - taskid: a1dea0eb-dd8b-487f-88e3-2e101a938bb9 + taskid: dac5aae1-1613-4584-8948-8b6f2e08db2f type: title task: - id: a1dea0eb-dd8b-487f-88e3-2e101a938bb9 + id: dac5aae1-1613-4584-8948-8b6f2e08db2f version: -1 name: Create asset type: title @@ -836,22 +900,27 @@ tasks: '#none#': - "23" separatecontext: false + continueonerrortype: "" view: |- { "position": { - "x": 50, - "y": 370 + "x": 40, + "y": -450 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "23": id: "23" - taskid: 5441f6c7-c4c5-4188-83d6-f21dfb1f8711 + taskid: aa344557-9b9b-4d8f-835f-fbaa4dbdad4f type: regular task: - id: 5441f6c7-c4c5-4188-83d6-f21dfb1f8711 + id: aa344557-9b9b-4d8f-835f-fbaa4dbdad4f version: -1 name: Get current time for name description: | @@ -862,31 +931,29 @@ tasks: brand: "" nexttasks: '#none#': - - "25" - scriptarguments: - contextKey: {} - date: {} - dateFormat: {} - hoursAgo: {} - minutesAgo: {} - monthsAgo: {} + - "54" separatecontext: false + continueonerrortype: "" view: |- { "position": { - "x": 50, - "y": 515 + "x": 40, + "y": -320 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "24": id: "24" - taskid: a418d595-7260-4a01-88ef-d5930df51efa + taskid: 94efed27-a06e-424a-8392-ec87036397d5 type: regular task: - id: a418d595-7260-4a01-88ef-d5930df51efa + id: 94efed27-a06e-424a-8392-ec87036397d5 version: -1 name: Create asset description: Create an Asset in Tenable.sc with provided IP addresses @@ -896,7 +963,7 @@ tasks: brand: Tenable.sc nexttasks: '#none#': - - "1" + - "57" scriptarguments: description: simple: desc @@ -906,24 +973,28 @@ tasks: simple: test_asset_${TimeNow} owner_id: simple: ${TenableSC.User.ID} - tag: {} separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 865 + "y": 520 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "25": id: "25" - taskid: 7354a486-e978-49c6-8acf-55c75e4bde15 + taskid: 3c64770c-124e-4406-8bcf-b3f2e6a00809 type: regular task: - id: 7354a486-e978-49c6-8acf-55c75e4bde15 + id: 3c64770c-124e-4406-8bcf-b3f2e6a00809 version: -1 name: Get user description: List users in Tenable.sc @@ -933,29 +1004,32 @@ tasks: brand: Tenable.sc nexttasks: '#none#': - - "24" + - "56" scriptarguments: - email: {} - id: {} - username: - simple: API55 + id: + simple: ${TenableSC.User.ID} separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 690 + "y": 180 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "26": id: "26" - taskid: 3a9d8b7c-528e-4bed-8ccc-5543bab14cac + taskid: 67feff1c-10d7-45b2-8ae6-5319ec4cc3aa type: regular task: - id: 3a9d8b7c-528e-4bed-8ccc-5543bab14cac + id: 67feff1c-10d7-45b2-8ae6-5319ec4cc3aa version: -1 name: Delete the asset description: Delete the Asset with the given ID from Tenable.sc @@ -970,22 +1044,27 @@ tasks: asset_id: simple: ${TenableSC.Asset.ID} separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 2760 + "y": 2950 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "27": id: "27" - taskid: 979a3870-6ef4-4430-8ab6-cb6780cd0239 + taskid: 8db98577-36ed-4147-8858-517ec3d312fd type: title task: - id: 979a3870-6ef4-4430-8ab6-cb6780cd0239 + id: 8db98577-36ed-4147-8858-517ec3d312fd version: -1 name: Alerts type: title @@ -995,6 +1074,7 @@ tasks: '#none#': - "28" separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -1005,12 +1085,16 @@ tasks: note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "28": id: "28" - taskid: ec34c2dd-8fd7-4975-8519-642c56b117c7 + taskid: f9ff94ff-2dbf-4a81-89dd-aa397f57a617 type: regular task: - id: ec34c2dd-8fd7-4975-8519-642c56b117c7 + id: f9ff94ff-2dbf-4a81-89dd-aa397f57a617 version: -1 name: Get manageable alerts description: List alerts from Tenable.sc @@ -1025,6 +1109,7 @@ tasks: manageable: simple: "true" separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -1035,12 +1120,16 @@ tasks: note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "29": id: "29" - taskid: 47cb6e45-a9ac-4d0e-8428-0817de5ba3d7 + taskid: 6b8f5c5f-9380-4ec3-8c21-bb510a9f6462 type: regular task: - id: 47cb6e45-a9ac-4d0e-8428-0817de5ba3d7 + id: 6b8f5c5f-9380-4ec3-8c21-bb510a9f6462 version: -1 name: Get alert description: Get information about a given alert in tenabel.sc @@ -1055,6 +1144,7 @@ tasks: alert_id: simple: ${TenableSC.Alert.ID} separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -1065,12 +1155,16 @@ tasks: note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "30": id: "30" - taskid: 40ad21b4-4727-4559-8914-f8c810f88f1a + taskid: 76fb6551-0464-4881-8581-b7f9b293a53e type: regular task: - id: 40ad21b4-4727-4559-8914-f8c810f88f1a + id: 76fb6551-0464-4881-8581-b7f9b293a53e version: -1 name: FetchFromInstance scriptName: FetchFromInstance @@ -1093,8 +1187,17 @@ tasks: right: value: simple: Tenable.sc + - - operator: isEqualString + left: + value: + simple: modules.state + iscontext: true + right: + value: + simple: active accessor: name separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -1105,12 +1208,16 @@ tasks: note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "31": id: "31" - taskid: b46887d4-2770-4a82-862b-1aceda14fe7a + taskid: aa036c4f-5a5e-422b-8625-6db308bc59ee type: title task: - id: b46887d4-2770-4a82-862b-1aceda14fe7a + id: aa036c4f-5a5e-422b-8625-6db308bc59ee version: -1 name: List items type: title @@ -1120,6 +1227,7 @@ tasks: '#none#': - "32" separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -1130,12 +1238,16 @@ tasks: note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "32": id: "32" - taskid: 384a8536-7c96-4ba0-8dee-c4d93834d69f + taskid: f4f64e1d-5978-4e2d-8058-0e9cd054e361 type: regular task: - id: 384a8536-7c96-4ba0-8dee-c4d93834d69f + id: f4f64e1d-5978-4e2d-8058-0e9cd054e361 version: -1 name: List assets description: Get a list of Tenable.sc Assets @@ -1150,6 +1262,7 @@ tasks: manageable: simple: "true" separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -1160,12 +1273,16 @@ tasks: note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "33": id: "33" - taskid: 6de2620e-554f-4c04-8ac4-aa997d3fff46 + taskid: ebfd3c31-d9f0-4174-89dc-ce705abd154a type: regular task: - id: 6de2620e-554f-4c04-8ac4-aa997d3fff46 + id: ebfd3c31-d9f0-4174-89dc-ce705abd154a version: -1 name: Delete Context description: Delete field from context @@ -1179,11 +1296,8 @@ tasks: scriptarguments: all: simple: "yes" - index: {} - key: {} - keysToKeep: {} - subplaybook: {} separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -1194,12 +1308,16 @@ tasks: note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "35": id: "35" - taskid: e8b94844-9b91-414c-8ae4-8f4de3cbabc7 + taskid: 87e78dcc-d7b5-4ab3-8629-2370a33120ac type: regular task: - id: e8b94844-9b91-414c-8ae4-8f4de3cbabc7 + id: 87e78dcc-d7b5-4ab3-8629-2370a33120ac version: -1 name: Get device description: Gets the device information from the current user in Tenable.sc @@ -1211,10 +1329,10 @@ tasks: '#none#': - "36" scriptarguments: - dnsName: {} ip: simple: 10.0.0.4 separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -1225,12 +1343,16 @@ tasks: note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "36": id: "36" - taskid: 2504a6f6-3dbc-4ff8-8593-d02b4bcb59f8 + taskid: 069fd62f-a89a-492b-8fdf-340c342be11c type: regular task: - id: 2504a6f6-3dbc-4ff8-8593-d02b4bcb59f8 + id: 069fd62f-a89a-492b-8fdf-340c342be11c version: -1 name: Get licensing description: Retrieve licensing information from Tenable.sc @@ -1242,6 +1364,7 @@ tasks: '#none#': - "37" separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -1252,12 +1375,16 @@ tasks: note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "37": id: "37" - taskid: c53774e0-71ab-4047-8057-2df69b5804d1 + taskid: f17c2296-f04e-4507-8d91-bf420d6f535c type: regular task: - id: c53774e0-71ab-4047-8057-2df69b5804d1 + id: f17c2296-f04e-4507-8d91-bf420d6f535c version: -1 name: Verify lists description: |- @@ -1274,12 +1401,12 @@ tasks: '#none#': - "38" scriptarguments: - expectedValue: {} fields: simple: Status,Device,Asset path: simple: TenableSC separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -1290,12 +1417,16 @@ tasks: note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "38": id: "38" - taskid: 958d2282-4915-479e-854a-265135a0f7ae + taskid: 978859ae-3512-4a5d-827d-1902181a26dc type: regular task: - id: 958d2282-4915-479e-854a-265135a0f7ae + id: 978859ae-3512-4a5d-827d-1902181a26dc version: -1 name: Verify Endpoint description: |- @@ -1310,14 +1441,14 @@ tasks: brand: "" nexttasks: '#none#': - - "42" + - "48" scriptarguments: expectedValue: simple: 10.0.0.4 - fields: {} path: simple: Endpoint.IPAddress separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -1328,12 +1459,16 @@ tasks: note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "40": id: "40" - taskid: 8ecf6b40-a8f9-4f0a-8443-88c0dee34226 + taskid: f6162da5-4007-4e85-88c5-8afcc5ad220b type: regular task: - id: 8ecf6b40-a8f9-4f0a-8443-88c0dee34226 + id: f6162da5-4007-4e85-88c5-8afcc5ad220b version: -1 name: Get the asset description: Get details for a given asset in Tenable.sc @@ -1343,27 +1478,32 @@ tasks: brand: Tenable.sc nexttasks: '#none#': - - "26" + - "47" scriptarguments: asset_id: simple: ${TenableSC.Asset.ID} separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 2585 + "y": 2595 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "41": id: "41" - taskid: 38c5d789-d4f4-4d9b-8205-f9203fdd3ca5 + taskid: c6a4a7f2-60f8-47af-8fa3-049d6f91b783 type: regular task: - id: 38c5d789-d4f4-4d9b-8205-f9203fdd3ca5 + id: c6a4a7f2-60f8-47af-8fa3-049d6f91b783 version: -1 name: Verify Alert description: |- @@ -1380,12 +1520,12 @@ tasks: '#none#': - "30" scriptarguments: - expectedValue: {} fields: simple: Actions,Action path: simple: TenableSC.Alert separatecontext: false + continueonerrortype: "" view: |- { "position": { @@ -1396,34 +1536,43 @@ tasks: note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "42": id: "42" - taskid: 668a3d13-4f9f-4832-8605-abec07b7a497 + taskid: 4d9cd97f-1bc6-412c-8e44-5d3b134933a4 type: title task: - id: 668a3d13-4f9f-4832-8605-abec07b7a497 + id: 4d9cd97f-1bc6-412c-8e44-5d3b134933a4 version: -1 name: Done type: title iscommand: false brand: "" separatecontext: false + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 7190 + "y": 8550 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false "43": id: "43" - taskid: 45bbd549-7c07-4b6e-8993-5addfebee830 + taskid: 624721d5-34c0-4b97-8296-ecea982e7639 type: regular task: - id: 45bbd549-7c07-4b6e-8993-5addfebee830 + id: 624721d5-34c0-4b97-8296-ecea982e7639 version: -1 name: Get all scan results description: Get all scan results in Tenable.sc @@ -1434,67 +1583,562 @@ tasks: nexttasks: '#none#': - "18" + separatecontext: false + continueonerrortype: "" + view: |- + { + "position": { + "x": 50, + "y": 4290 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "46": + id: "46" + taskid: 1cb46508-ccae-4720-8894-470ec0c257cf + type: regular + task: + id: 1cb46508-ccae-4720-8894-470ec0c257cf + version: -1 + name: Update asset description + description: Requires security manager role. Update an asset. + script: '|||tenable-sc-update-asset' + type: regular + iscommand: true + brand: "" + nexttasks: + '#none#': + - "40" scriptarguments: - manageable: {} + asset_id: + simple: ${TenableSC.Asset.ID} + description: + simple: This is my test asset. + separatecontext: false + continueonerrortype: "" + view: |- + { + "position": { + "x": 50, + "y": 2410 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "47": + id: "47" + taskid: 98c6e89a-03e0-4c7c-8f86-88c17fc46f27 + type: condition + task: + id: 98c6e89a-03e0-4c7c-8f86-88c17fc46f27 + version: -1 + name: Verify update asset + type: condition + iscommand: false + brand: "" + nexttasks: + "yes": + - "26" separatecontext: false + conditions: + - label: "yes" + condition: + - - operator: isEqualString + left: + value: + simple: TenableSC.Asset.Description + iscontext: true + right: + value: + simple: This is my test asset. + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 4100 + "y": 2780 } } note: false timertriggers: [] ignoreworker: false - "45": - id: "45" - taskid: 41673689-90ac-442e-854b-14684447e1fe + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "48": + id: "48" + taskid: 1d51fc88-f726-4800-80f1-269f7676ef39 type: regular task: - id: 41673689-90ac-442e-854b-14684447e1fe + id: 1d51fc88-f726-4800-80f1-269f7676ef39 version: -1 - name: Verify CVE - description: |- - Deprecated, use playbook filters instead. - Verifies path in context: - - Verifies path existence - - If matching object is an array: verify fields exists in each of the objects in the array - - If matching object is not an array: verify fields exists in matching object - - if 'expectedValue' is given: ensure that the given value is equal to the context path - scriptName: VerifyContext + name: List queries + description: Requires security manager role. Lists queries. + script: '|||tenable-sc-list-query' type: regular + iscommand: true + brand: "" + nexttasks: + '#none#': + - "49" + separatecontext: false + continueonerrortype: "" + view: |- + { + "position": { + "x": 50, + "y": 7180 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "49": + id: "49" + taskid: 29ce3233-cd78-4e7d-8396-25fa889a561a + type: condition + task: + id: 29ce3233-cd78-4e7d-8396-25fa889a561a + version: -1 + name: Verify list queries + type: condition iscommand: false brand: "" + nexttasks: + "yes": + - "50" + separatecontext: false + conditions: + - label: "yes" + condition: + - - operator: isNotEmpty + left: + value: + simple: TenableSC.Query.Manageable.ID + iscontext: true + continueonerrortype: "" + view: |- + { + "position": { + "x": 50, + "y": 7350 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "50": + id: "50" + taskid: 85140e28-1f6b-465f-8d92-04213328dcfa + type: regular + task: + id: 85140e28-1f6b-465f-8d92-04213328dcfa + version: -1 + name: Create policy + description: Requires security manager role. This command is prerequisite for + creating remediation scan. creates policy. + script: '|||tenable-sc-create-policy' + type: regular + iscommand: true + brand: "" nexttasks: '#none#': - - "21" + - "51" scriptarguments: - expectedValue: {} - fields: - simple: ID - path: - simple: CVE + family_id: + simple: "1" + plugins_id: + simple: "1" + policy_template_id: + simple: "1" + separatecontext: false + continueonerrortype: "" + view: |- + { + "position": { + "x": 50, + "y": 7520 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "51": + id: "51" + taskid: 47ee30bd-4ccb-4d0b-8529-7fbfc031c5da + type: condition + task: + id: 47ee30bd-4ccb-4d0b-8529-7fbfc031c5da + version: -1 + name: Verify create policy + type: condition + iscommand: false + brand: "" + nexttasks: + "yes": + - "52" + separatecontext: false + conditions: + - label: "yes" + condition: + - - operator: isExists + left: + value: + simple: TenableSC.ScanPolicy.ID + iscontext: true + continueonerrortype: "" + view: |- + { + "position": { + "x": 50, + "y": 7700 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "52": + id: "52" + taskid: 8a5fe836-4146-411d-8e50-22c605b4aa1f + type: regular + task: + id: 8a5fe836-4146-411d-8e50-22c605b4aa1f + version: -1 + name: List plugin family + description: Requires security manager role. list plugin families / return information + about a plugin family given ID. + script: '|||tenable-sc-list-plugin-family' + type: regular + iscommand: true + brand: "" + nexttasks: + '#none#': + - "53" + separatecontext: false + continueonerrortype: "" + view: |- + { + "position": { + "x": 50, + "y": 7880 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "53": + id: "53" + taskid: 0e2b92e9-1cf4-4040-8d5a-99184f84e926 + type: condition + task: + id: 0e2b92e9-1cf4-4040-8d5a-99184f84e926 + version: -1 + name: Verify list plugin + type: condition + iscommand: false + brand: "" + nexttasks: + "yes": + - "58" + separatecontext: false + conditions: + - label: "yes" + condition: + - - operator: isNotEmpty + left: + value: + simple: TenableSC.PluginFamily.ID + iscontext: true + continueonerrortype: "" + view: |- + { + "position": { + "x": 50, + "y": 8050 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "54": + id: "54" + taskid: 82b6d961-5c84-4f52-8f2c-ad97a75ae869 + type: regular + task: + id: 82b6d961-5c84-4f52-8f2c-ad97a75ae869 + version: -1 + name: Create user + description: This command can be executed with both roles (admin or security + manager) based on the roll_id you with to choose. Creates a new user. + script: '|||tenable-sc-create-user' + type: regular + iscommand: true + brand: "" + nexttasks: + '#none#': + - "55" + scriptarguments: + last_name: + simple: test + password: + simple: test + role_id: + simple: "4" + user_name: + simple: test_user_${TimeNow} + separatecontext: false + continueonerrortype: "" + view: |- + { + "position": { + "x": 50, + "y": -160 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "55": + id: "55" + taskid: ad3f2a34-a8b0-4753-88ae-e851aeed622b + type: regular + task: + id: ad3f2a34-a8b0-4753-88ae-e851aeed622b + version: -1 + name: Update user + description: update user details by given user_id. + script: Tenable.sc|||tenable-sc-update-user + type: regular + iscommand: true + brand: Tenable.sc + nexttasks: + '#none#': + - "25" + scriptarguments: + first_name: + simple: test + user_id: + simple: ${TenableSC.User.ID} + separatecontext: false + continueonerrortype: "" + view: |- + { + "position": { + "x": 50, + "y": 15 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "56": + id: "56" + taskid: 27abd085-6f81-4757-8440-bb670a035be8 + type: condition + task: + id: 27abd085-6f81-4757-8440-bb670a035be8 + version: -1 + name: Verify create and update user + type: condition + iscommand: false + brand: "" + nexttasks: + "yes": + - "24" + separatecontext: false + conditions: + - label: "yes" + condition: + - - operator: isEqualString + left: + value: + simple: TenableSC.User.FirstName + iscontext: true + right: + value: + simple: test + - - operator: isEqualString + left: + value: + simple: TenableSC.User.LastName + iscontext: true + right: + value: + simple: test + continueonerrortype: "" + view: |- + { + "position": { + "x": 50, + "y": 350 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "57": + id: "57" + taskid: b5d42fc5-719a-4e5a-88a7-fb1bfcf35aa6 + type: regular + task: + id: b5d42fc5-719a-4e5a-88a7-fb1bfcf35aa6 + version: -1 + name: Delete user + description: This command can be executed with both roles (admin or security + manager). Delete a user by given user_id. + script: '|||tenable-sc-delete-user' + type: regular + iscommand: true + brand: "" + nexttasks: + '#none#': + - "1" + scriptarguments: + user_id: + simple: ${TenableSC.User.ID} + separatecontext: false + continueonerrortype: "" + view: |- + { + "position": { + "x": 50, + "y": 690 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "58": + id: "58" + taskid: 465cc62d-1a98-4b6c-80f4-9e77e0291602 + type: regular + task: + id: 465cc62d-1a98-4b6c-80f4-9e77e0291602 + version: -1 + name: List groups + description: Requires security manager role. list all groups. + script: '|||tenable-sc-list-groups' + type: regular + iscommand: true + brand: "" + nexttasks: + '#none#': + - "59" separatecontext: false + continueonerrortype: "" + view: |- + { + "position": { + "x": 50, + "y": 8220 + } + } + note: false + timertriggers: [] + ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false + "59": + id: "59" + taskid: 6d56b3fc-ac11-49e8-8f58-0c0d7e5d0a29 + type: condition + task: + id: 6d56b3fc-ac11-49e8-8f58-0c0d7e5d0a29 + version: -1 + name: Verify list groups + type: condition + iscommand: false + brand: "" + nexttasks: + "yes": + - "42" + separatecontext: false + conditions: + - label: "yes" + condition: + - - operator: isNotEmpty + left: + value: + simple: TenableSC.Group.ID + iscontext: true + continueonerrortype: "" view: |- { "position": { "x": 50, - "y": 4800 + "y": 8380 } } note: false timertriggers: [] ignoreworker: false + skipunavailable: false + quietmode: 0 + isoversize: false + isautoswitchedtoquietmode: false view: |- { "linkLabelsPosition": {}, "paper": { "dimensions": { - "height": 7205, - "width": 380, - "x": 50, - "y": 50 + "height": 9365, + "width": 390, + "x": 40, + "y": -750 } } } diff --git a/Tests/conf.json b/Tests/conf.json index f5ea9ea63da7..75510d8f37e8 100644 --- a/Tests/conf.json +++ b/Tests/conf.json @@ -1071,6 +1071,7 @@ { "integrations": "Tenable.sc", "playbookID": "tenable-sc-test", + "instance_names": "Tenable.sc", "timeout": 240 }, { @@ -1189,11 +1190,6 @@ "playbookID": "Tenable.io Scan Test", "timeout": 3600 }, - { - "integrations": "Tenable.sc", - "playbookID": "tenable-sc-scan-test", - "timeout": 600 - }, { "integrations": "google-vault", "playbookID": "Google-Vault-Generic-Test", @@ -5714,7 +5710,6 @@ "Cisco Umbrella Reporting": "No instance - developed by Login-Soft", "FortinetFortiwebVM": "No instance - developed by Qmasters", "_comment2": "~~~ UNSTABLE ~~~", - "Tenable.sc": "unstable instance", "ThreatConnect v2": "unstable instance", "_comment3": "~~~ QUOTA ISSUES ~~~", "Lastline": "issue 20323", From d8626aa89e164fd271f2411cd555da519d5b157c Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Sun, 28 May 2023 17:28:47 +0300 Subject: [PATCH 046/115] added tpb --- Tests/conf.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Tests/conf.json b/Tests/conf.json index 75510d8f37e8..54b44a5f9c65 100644 --- a/Tests/conf.json +++ b/Tests/conf.json @@ -1071,7 +1071,13 @@ { "integrations": "Tenable.sc", "playbookID": "tenable-sc-test", - "instance_names": "Tenable.sc", + "instance_names": "Tenable_SC_secman_username_and_password", + "timeout": 240 + }, + { + "integrations": "Tenable.sc", + "playbookID": "tenable-sc-test", + "instance_names": "Tenable_SC_secman_api_key", "timeout": 240 }, { From 6ae4bb3bf6147eb6d27754c85d3e26f10d184843 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Sun, 28 May 2023 17:39:20 +0300 Subject: [PATCH 047/115] fixes --- .../Integrations/Tenable_sc/Tenable_sc.py | 18 +++++++++--------- .../Tenable_sc/test_data/test_get_query.json | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 126a9491cf77..349b574bda92 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -478,7 +478,7 @@ def create_asset(self, name, description, owner_id, tags, ips): Args: name (str): The name for the asset. description (str): The description for the asset. - owner_id (str): The Id of the owner of the asset. + owner_id (str): The ID of the owner of the asset. tags (str): The tags for the asset. ips (str): The IP list for the asset. Returns: @@ -2479,7 +2479,7 @@ def process_update_and_create_user_response(res, hr_header): """ if not res or not res.get('response', {}): raise DemistoException("User wasn't created successfully.") - headers = ["User type", "User Id", "User Status", "User Name", "First Name", "Lat Name ", "Email ", "User Role Name", + headers = ["User type", "User ID", "User Status", "User Name", "First Name", "Lat Name ", "Email ", "User Role Name", "User Group Name", "User LDAP Name"] response = res.get("response", {}) mapped_response = { @@ -2584,7 +2584,7 @@ def create_policy_command(client: Client, args: Dict[str, Any]): "policyTemplate ID": created_policy.get("policyTemplate", {}).get("id"), "policyTemplate Name": created_policy.get("policyTemplate", {}).get("name") } - headers = ["Policy type", "Policy Id", "name", "Description", "Created Time", "Plugin Families", "Policy Status", + headers = ["Policy type", "Policy ID", "name", "Description", "Created Time", "Plugin Families", "Policy Status", "Policy UUID", "Policy can Manage", "Creator Username", "Owner ID", "policyTemplate id", "policyTemplate Name"] @@ -2711,12 +2711,12 @@ def get_query(client: Client, query_id): raise DemistoException(f"The query {query_id} wasn't found") query = res.get('response') mapped_query = { - "Query Id": query_id, + "Query ID": query_id, "Query Name": query.get("name"), "Query Description": query.get("description"), "Query Filters": query.get("filters") } - headers = ["Query Id", "Query Name", "Query Description", "Query Filters"] + headers = ["Query ID", "Query Name", "Query Description", "Query Filters"] hr = tableToMarkdown(f'Query {query_id}', mapped_query, headers, removeNull=True) return res, hr, query @@ -2744,7 +2744,7 @@ def list_queries(client: Client, type): for manageable_query in manageable_queries: query_id = manageable_query.get("id") mapped_queries.append({ - "Query Id": query_id, + "Query ID": query_id, "Query Name": manageable_query.get("name"), "Query Description": manageable_query.get("description"), "Query Filters": manageable_query.get("filters"), @@ -2755,7 +2755,7 @@ def list_queries(client: Client, type): query_id = usable_query.get("id") if query_id not in found_ids: mapped_usable_queries.append({ - "Query Id": usable_query.get("id"), + "Query ID": usable_query.get("id"), "Query Name": usable_query.get("name"), "Query Description": usable_query.get("description"), "Query Filters": usable_query.get("filters"), @@ -2764,7 +2764,7 @@ def list_queries(client: Client, type): }) else: for mapped_query in mapped_queries: - if query_id == mapped_query["Query Id"]: + if query_id == mapped_query["Query ID"]: mapped_query["Query Usable"] = "True" for mapped_query in mapped_queries: @@ -2772,7 +2772,7 @@ def list_queries(client: Client, type): mapped_query["Query Usable"] = "False" mapped_queries.extend(mapped_usable_queries) - headers = ["Query Id", "Query Name", "Query Description", "Query Filters", "Query Manageable", "Query Usable"] + headers = ["Query ID", "Query Name", "Query Description", "Query Filters", "Query Manageable", "Query Usable"] hr = tableToMarkdown('Queries:', mapped_queries, headers, removeNull=True) return res, hr, queries diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_get_query.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_get_query.json index d03ff416162f..f47a77762313 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_get_query.json +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_get_query.json @@ -2,7 +2,7 @@ "test_case_1": { "query_id": "test_id", "mock_response": {"error_code":0,"error_msg":"","response":{"id": "test_id", "name": "test_name", "description": "test_description", "filters": null}, "timestamp":1684676253,"type":"regular","warnings":[]}, - "expected_hr": "### Query test_id\n|Query Id|Query Name|Query Description|\n|---|---|---|\n| test_id | test_name | test_description |\n", + "expected_hr": "### Query test_id\n|Query ID|Query Name|Query Description|\n|---|---|---|\n| test_id | test_name | test_description |\n", "expected_ec": {"id": "test_id", "name": "test_name", "description": "test_description", "filters": null} } } \ No newline at end of file From 4e5451b16f3214640ac38f4b45d08cb9abad0969 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Mon, 29 May 2023 09:47:11 +0300 Subject: [PATCH 048/115] fixes --- Packs/Tenable_sc/Integrations/Tenable_sc/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/README.md b/Packs/Tenable_sc/Integrations/Tenable_sc/README.md index 36dd36d9fdab..a5660223cd44 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/README.md +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/README.md @@ -914,6 +914,7 @@ Requires security manager authentication. Returns all scan results in Tenable.sc | TenableSC.ScanResults.ScannedIPs | number | Number of scanned IPs. | | TenableSC.ScanResults.Owner | string | Scan owner name. | | TenableSC.ScanResults.RepositoryName | string | Scan repository name. | +| TenableSC.ScanResults.ImportStatus | string | Scan import status. | #### Human Readable Output From 062db23f5cf6e4fd2243207c9abf08f695e8152e Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Mon, 29 May 2023 10:46:23 +0300 Subject: [PATCH 049/115] fixes --- .../test_data/test_list_groups_command.json | 16 ++++++++-------- .../Tenable_sc/test_data/test_list_queries.json | 2 +- .../test_data/test_list_query_command.json | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_groups_command.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_groups_command.json index 1940cc6b5e9e..7fa4640b9eca 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_groups_command.json +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_groups_command.json @@ -8,11 +8,11 @@ }, "expected_hr": "### Tenable.sc groups\n|ID|\n|---|\n| 0 |\n### Group id:0\n|Username|Firstname|\n|---|---|\n| test | test |\n\n", "expected_ec": [{ - "Id": "0", + "ID": "0", "Users": [ { "Firstname": "test", - "Id": "1", + "ID": "1", "Lastname": "", "Username": "test", "Uuid": "uuid" @@ -30,10 +30,10 @@ }, "expected_hr": "### Tenable.sc groups\n|ID|\n|---|\n| 0 |\n| 1 |\n", "expected_ec": [{ - "Id": "0" + "ID": "0" }, { - "Id": "1" + "ID": "1" } ] }, @@ -46,22 +46,22 @@ }, "expected_hr": "### Tenable.sc groups\n|ID|\n|---|\n| 0 |\n| 1 |\n### Group id:0\n|Username|Firstname|\n|---|---|\n| test | test |\n\n### Group id:1\n|Username|Firstname|\n|---|---|\n| test | test |\n\n", "expected_ec": [{ - "Id": "0", + "ID": "0", "Users": [ { "Firstname": "test", - "Id": "1", + "ID": "1", "Lastname": "", "Username": "test", "Uuid": "uuid" } ] },{ - "Id": "1", + "ID": "1", "Users": [ { "Firstname": "test", - "Id": "1", + "ID": "1", "Lastname": "", "Username": "test", "Uuid": "uuid2" diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_queries.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_queries.json index 2468c33bb083..36a3d6621a78 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_queries.json +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_queries.json @@ -18,7 +18,7 @@ "id": "3", "name": "test_name", "description": "test_description", "filters": null } ]}, "timestamp":1684676253,"type":"regular","warnings":[]}, - "expected_hr": "### Queries:\n|Query Id|Query Name|Query Description|Query Filters|Query Manageable|Query Usable|\n|---|---|---|---|---|---|\n| 1 | test_name | test_description | filter | True | True |\n| 2 | test_name | test_description | | True | False |\n| 3 | test_name | test_description | | False | True |\n", + "expected_hr": "### Queries:\n|Query ID|Query Name|Query Description|Query Filters|Query Manageable|Query Usable|\n|---|---|---|---|---|---|\n| 1 | test_name | test_description | filter | True | True |\n| 2 | test_name | test_description | | True | False |\n| 3 | test_name | test_description | | False | True |\n", "expected_ec": {"manageable": [{"id": "1", "name": "test_name", "description": "test_description", "filters": "filter"}, {"id": "2", "name": "test_name", "description": "test_description", "filters": null}], "usable": [{"id": "1", "name": "test_name", "description": "test_description", "filters": "filter"}, {"id": "3", "name": "test_name", "description": "test_description", "filters": null}]} } diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_query_command.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_query_command.json index 14e81644db11..9dac4ac52692 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_query_command.json +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_query_command.json @@ -20,7 +20,7 @@ "id": "3", "name": "test_name", "description": "test_description", "filters": null } ]}, "timestamp":1684676253,"type":"regular","warnings":[]}, - "expected_hr": "### Queries:\n|Query Id|Query Name|Query Description|Query Filters|Query Manageable|Query Usable|\n|---|---|---|---|---|---|\n| 1 | test_name | test_description | filter | True | True |\n| 2 | test_name | test_description | | True | False |\n| 3 | test_name | test_description | | False | True |\n", + "expected_hr": "### Queries:\n|Query ID|Query Name|Query Description|Query Filters|Query Manageable|Query Usable|\n|---|---|---|---|---|---|\n| 1 | test_name | test_description | filter | True | True |\n| 2 | test_name | test_description | | True | False |\n| 3 | test_name | test_description | | False | True |\n", "expected_ec": {"Manageable": [{"ID": "1", "Name": "test_name", "Description": "test_description", "Filters": "filter"}, {"ID": "2", "Name": "test_name", "Description": "test_description", "Filters": null}], "Usable": [{"ID": "1", "Name": "test_name", "Description": "test_description", "Filters": "filter"}, {"ID": "3", "Name": "test_name", "Description": "test_description", "Filters": null}]} }, @@ -28,7 +28,7 @@ "args": {"query_id": "test_id"}, "mocked_function": "get_query", "mock_response": {"error_code":0,"error_msg":"","response":{"id": "test_id", "name": "test_name", "description": "test_description", "filters": null}, "timestamp":1684676253,"type":"regular","warnings":[]}, - "expected_hr": "### Query test_id\n|Query Id|Query Name|Query Description|\n|---|---|---|\n| test_id | test_name | test_description |\n", + "expected_hr": "### Query test_id\n|Query ID|Query Name|Query Description|\n|---|---|---|\n| test_id | test_name | test_description |\n", "expected_ec": {"ID": "test_id", "Name": "test_name", "Description": "test_description"} } } \ No newline at end of file From 18ffeb78de2414917a27e1afe1827f7c515216e4 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Mon, 29 May 2023 11:16:04 +0300 Subject: [PATCH 050/115] fixes --- .../Integrations/Tenable_sc/Tenable_sc.py | 10 +++++----- .../test_data/test_list_groups_command.json | 20 +++++++++++-------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py index 349b574bda92..de252fb0823b 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/Tenable_sc.py @@ -2364,11 +2364,11 @@ def list_groups_command(client: Client, args: Dict[str, Any]): users = [] for index, group in enumerate(groups): users = [{ - 'Username': user['username'], - 'Firstname': user['firstname'], - 'Lastname': user['lastname'], - 'ID': user['id'], - 'UUID': user['uuid'] + 'Username': user.get('username', ""), + 'Firstname': user.get('firstname', ""), + 'Lastname': user.get('lastname', ""), + 'ID': user.get('id', ""), + 'UUID': user.get('UUID', "") } for user in group.get('users')] mapped_groups[index]['Users'] = users group_id = group.get('id') diff --git a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_groups_command.json b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_groups_command.json index 7fa4640b9eca..42a2fb3cb675 100644 --- a/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_groups_command.json +++ b/Packs/Tenable_sc/Integrations/Tenable_sc/test_data/test_list_groups_command.json @@ -15,7 +15,7 @@ "ID": "1", "Lastname": "", "Username": "test", - "Uuid": "uuid" + "UUID": "uuid" } ] } @@ -24,16 +24,20 @@ "test_case_2": { "args": {"show_users": "False"}, "mock_response": { - "error_code":0, "error_msg":"", "response": [{"id":"0", "users": [{"firstname":"test","id":"1","lastname":"","username":"test","uuid":"uuid"}]}, - {"id":"1", "users": [{"firstname":"test","id":"1","lastname":"","username":"test","uuid":"uuid2"}]}], + "error_code":0, "error_msg":"", "response": [{"id":"0", "description": "Full Access group", "name": "Full Access"}, + {"id":"1", "description": "Full Access group", "name": "Full Access"}], "timestamp":1684672117,"type":"regular","warnings":[] }, - "expected_hr": "### Tenable.sc groups\n|ID|\n|---|\n| 0 |\n| 1 |\n", + "expected_hr": "### Tenable.sc groups\n|ID|Name|Description|\n|---|---|---|\n| 0 | Full Access | Full Access group |\n| 1 | Full Access | Full Access group |\n", "expected_ec": [{ - "ID": "0" + "ID": "0", + "Description": "Full Access group", + "Name": "Full Access" }, { - "ID": "1" + "ID": "1", + "Description": "Full Access group", + "Name": "Full Access" } ] }, @@ -53,7 +57,7 @@ "ID": "1", "Lastname": "", "Username": "test", - "Uuid": "uuid" + "UUID": "uuid" } ] },{ @@ -64,7 +68,7 @@ "ID": "1", "Lastname": "", "Username": "test", - "Uuid": "uuid2" + "UUID": "uuid2" } ] } From 0f926b10df8e348395a2572c9d8fcfb1fbdac5f6 Mon Sep 17 00:00:00 2001 From: YuvHayun Date: Mon, 29 May 2023 16:07:46 +0300 Subject: [PATCH 051/115] fix tpb issues --- .../integration-CrowdStrikeFalcon.yml | 8659 +++++++++++++++++ ...laybook-tenable.sc_test_secman_api_key.yml | 77 + ...e.sc_test_secman_username_and_password.yml | 78 + Tests/conf.json | 4 +- 4 files changed, 8816 insertions(+), 2 deletions(-) create mode 100644 Packs/CrowdStrikeFalcon/Integrations/CrowdStrikeFalcon/integration-CrowdStrikeFalcon.yml create mode 100644 Packs/Tenable_sc/TestPlaybooks/playbook-tenable.sc_test_secman_api_key.yml create mode 100644 Packs/Tenable_sc/TestPlaybooks/playbook-tenable.sc_test_secman_username_and_password.yml diff --git a/Packs/CrowdStrikeFalcon/Integrations/CrowdStrikeFalcon/integration-CrowdStrikeFalcon.yml b/Packs/CrowdStrikeFalcon/Integrations/CrowdStrikeFalcon/integration-CrowdStrikeFalcon.yml new file mode 100644 index 000000000000..ac4b1639f346 --- /dev/null +++ b/Packs/CrowdStrikeFalcon/Integrations/CrowdStrikeFalcon/integration-CrowdStrikeFalcon.yml @@ -0,0 +1,8659 @@ +category: Endpoint +sectionOrder: +- Connect +- Collect +commonfields: + id: CrowdstrikeFalcon + version: -1 +configuration: +- defaultvalue: https://api.crowdstrike.com + display: Server URL (e.g., https://api.crowdstrike.com) + name: url + required: true + type: 0 + section: Connect +- display: Client ID + name: credentials + required: false + type: 9 + displaypassword: Secret + section: Connect +- display: Client ID + name: client_id + required: false + type: 0 + hidden: true + section: Connect +- display: Secret + name: secret + required: false + type: 4 + hidden: true + section: Connect +- defaultvalue: 'A+ - 3rd party enrichment' + display: Source Reliability + name: Reliability + required: false + type: 15 + additionalinfo: Reliability of the source providing the intelligence data. Currently used for “CVE” reputation command. + options: + - A+ - 3rd party enrichment + - A - Completely reliable + - B - Usually reliable + - C - Fairly reliable + - D - Not usually reliable + - E - Unreliable + - F - Reliability cannot be judged + section: Collect + advanced: true +- display: First fetch timestamp (