Tenable.sc
With Tenable.sc (formerly SecurityCenter) you get a real-time, continuous assessment of your security posture so you can find and fix vulnerabilities faster.
Vulnerability Management · Tenable.sc
Details
| ID | Tenable.sc |
|---|---|
| Provider | Tenable |
| Category | Vulnerability Management |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM Exposure Management |
README
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 in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL (e.g. https://192.168.0.1) | The server URL. | True |
| Access key | See the help for instructions to generate the access key. | False |
| Secret key | False | |
| Username | The Username is either admin or secman (depend on the role you want 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) | The timestamp to start the fetch from. | False |
Commands
You can execute these commands from the 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 role. 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. |
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
Requires security manager role. Launch an existing scan from Tenable.sc. Set polling to true to follow the scan and receive results when scan is over.
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 |
| timeout_in_seconds | Relevant only when polling is true. Default is 3 hours. The timeout in seconds until polling ends. Default is 10800. | Optional |
| polling | Default is false. When set to true, will keep polling results until scan is done and return the formatted scan results. Possible values are: true, false. Default is false. | Optional |
| scan_results_id | Deprecated. Scan results ID. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| TenableSC.ScanResults.Name | string | Scan name. |
| TenableSC.ScanResults.Status | string | Scan status. |
| TenableSC.ScanResults.ID | string | Scan Results ID. |
| TenableSC.ScanResults.OwnerID | string | Relevant only when polling is false. Scan owner ID. |
| TenableSC.ScanResults.JobID | string | Relevant only when polling is false. Job ID. |
| TenableSC.ScanResults.ScannedIPs | number | Relevant only when polling is true. Scan number of scanned IPs. |
| TenableSC.ScanResults.StartTime | date | Relevant only when polling is true. Scan start time. |
| TenableSC.ScanResults.EndTime | date | Relevant only when polling is true. Scan end time. |
| TenableSC.ScanResults.Checks | number | Relevant only when polling is true. Scan completed checks. |
| TenableSC.ScanResults.RepositoryName | string | Relevant only when polling is true. Scan repository name. |
| TenableSC.ScanResults.Description | string | Relevant only when polling is true. Scan description. |
| TenableSC.ScanResults.Vulnerability.ID | number | Relevant only when polling is true. Scan vulnerability ID. |
| TenableSC.ScanResults.Vulnerability.Name | string | Relevant only when polling is true. Scan vulnerability Name. |
| TenableSC.ScanResults.Vulnerability.Family | string | Relevant only when polling is true. Scan vulnerability family. |
| TenableSC.ScanResults.Vulnerability.Severity | string | Relevant only when polling is true. Scan vulnerability severity. |
| TenableSC.ScanResults.Vulnerability.Total | number | Relevant only when polling is true. Scan vulnerability total hosts. |
| TenableSC.ScanResults.Policy | string | Relevant only when polling is true. Scan policy. |
| TenableSC.ScanResults.Group | string | Relevant only when polling is true. Scan owner group name. |
| TenableSC.ScanResults.Owner | string | Relevant only when polling is true. Scan owner user name. |
| TenableSC.ScanResults.Duration | number | Relevant only when polling is true. Scan duration in minutes. |
| TenableSC.ScanResults.ImportTime | date | Relevant only when polling is true. Scan import time. |
Human Readable Output
When polling is set to false:
Tenable.sc Scan
| Name | ID | OwnerID | JobID | Status |
|---|---|---|---|---|
| test_scan_2023 | 169 | 38 | 118864 | Queued |
When polling is set to true:
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 |
tenable-sc-get-vulnerability
Requires security manager role. 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 the tenable-sc-list-query command. | Optional |
| sort_direction | The direction in which the results should be sorted. 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, otherwise “query_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. |
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 |
|---|---|---|---|
| {IP} | {MAC} | 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
Requires security manager role. 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 | string | Scan description. |
| TenableSC.ScanResults.ID | string | Scan results ID. |
| TenableSC.ScanResults.Error | string | Will appear only in case of error in the scan, include the cause for the failure. |
Human Readable Output
Tenable.sc Scan Status
| ID | Name | Status | Description |
|---|---|---|---|
| 169 | test_scan_2023 | Running | Test scan 2023 |
tenable-sc-get-scan-report
Requires security manager role. Get a single report with Tenable.sc scan results. In case of Importstatus = Error (The results import wasn’t finished), the vulnerabilities section will not be added to the 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. |
| TenableSC.ScanResults.IsScanRunning | boolean | Whether the scan is still running. |
| TenableSC.ScanResults.ImportStatus | string | Scan import status. |
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
Requires security manager role. 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. |
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
Requires security manager role. 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. |
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
Requires security manager role. 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. |
Human Readable Output
Tenable.sc Report Definitions
| ID | Name | Description | Type | Group | Owner |
|---|---|---|---|---|---|
| 2 | Critical and Exploitable Vulnerabilities Report | Test | Full Access | test |
tenable-sc-list-repositories
Requires security manager role. 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. |
Human Readable Output
Tenable.sc Scan Repositories
| ID | Name |
|---|---|
| 1 | Local |
tenable-sc-list-zones
Requires admin role. 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. |
Human Readable Output
Tenable.sc Scan Zones
| ID | Name | IPList | activeScanners |
|---|---|---|---|
| 1 | Default Scan Zone | ip | 1 |
Tenable.sc Scanners
| ID | Name | Status |
|---|---|---|
| 2 | RHEL6 Scanner | 1 |
tenable-sc-create-scan
Requires security manager role. Create a scan on Tenable.sc
Base Command
tenable-sc-create-scan
Input
| Argument Name | Description | Required |
|---|---|---|
| name | Scan name. | Required |
| policy_id | Policy ID, can be retrieved from the list-policies command. | Required |
| plugin_id | Plugin ID. | Optional |
| description | Scan description. | Optional |
| repository_id | Scan Repository ID. Can be retrieved from the list-repositories command. | Required |
| zone_id | Scan zone ID (default is all zones). Can be retrieved from the 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 the list-assets command. Possible values are: All, AllManageable. | Optional |
| scan_virtual_hosts | Whether to include virtual hosts. Default is 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 the list-report-definitions command. | Optional |
| credentials | Comma-separated credentials IDs to use. Can be retrieved from the 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 the 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 in the format of YYYY-MM-DD:HH:MM:SS or relative timestamp (i.e., now, 3 days). | Optional |
| repeat_rule_freq | Specifies 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. 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. |
Human Readable Output
Scan created successfully
| ID | CreatorID | Name | Type | CreationTime |
|---|---|---|---|---|
| 70 | 39 | my_name | policy | 2023-05-24T12:33:03Z |
tenable-sc-delete-scan
Requires security manager role. 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 the list-scans command. | Required |
Context Output
There is no context output for this command.
Human Readable Output
Scan {scan_id} was deleted successfully.
tenable-sc-list-assets
Requires security manager role. 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. |
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
Requires security manager role. 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. |
Human Readable Output
Asset created successfully
| ID | Name | OwnerName |
|---|---|---|
| 42 | example output | yuv |
tenable-sc-get-asset
Requires security manager role. 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. |
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 | {IPs_list} |
tenable-sc-delete-asset
Requires security manager role. 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.
Human Readable Output
Asset {asset_id} was deleted successfully.
tenable-sc-list-alerts
Requires security manager role. 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. |
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
Requires security manager role. 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 the 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. |
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
Requires security manager role. 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 the 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. |
Human Readable Output
Tenable.sc Device
| IP | UUID | MacAddress |
|---|---|---|
| {IP} | {UUID} | {MacAddress} |
tenable-sc-list-users
List users in Tenable.sc. Results may vary based on the role type (admin or security manager).
Base Command
tenable-sc-list-users
Input
| Argument Name | Description | Required |
|---|---|---|
| id | Filter by user ID. | Optional |
| username | Filter by user username. | Optional |
| 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. |
Human Readable Output
Tenable.sc Users
| ID | Username | Title | 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
Retrieve licensing information from Tenable.sc. Requires admin role.
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. |
Human Readable Output
Tenable.sc Licensing information
| License | LicensedIPS | ActiveIPS |
|---|---|---|
| Valid | 512 | 152 |
tenable-sc-get-system-information
Get the system information and diagnostics from Tenable.sc. Requires admin role.
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 | Disk threshold. |
| TenableSC.System.LastCheck | date | System last check time. |
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
Returns all scan results in Tenable.sc. Requires security manager role.
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. |
| TenableSC.ScanResults.ImportStatus | string | Scan import status. |
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
List all groups. Requires security manager role.
Base Command
tenable-sc-list-groups
Input
| Argument Name | Description | Required |
|---|---|---|
| show_users | Whether to show group member. Possible values are: true, false. Default is true. | Optional |
| limit | The number of objects to return in one response. 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 username. |
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
Creates a new user. This command can be executed with both roles (admin or security manager) based on the role_id you choose.
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 |
| 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 | Whether the user should be locked. 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 | The authentication type. Tenable (TNS). Lightweight Directory Access Protocol (LDAP). Security Assertion Markup Language (SAML). LDAP server or SAML authentication needs 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. Only an Administrator can create Administrator accounts. Possible values are: Administrator, Security Manager, Security Analyst, Vulnerability Analyst, Executive, Credential Manager, Auditor. | Required |
| must_change_password | Whether the password must be changed. 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 that can manage groups. Use tenable-sc-list-groups to get all available groups. | Optional |
| managed_objects_groups | Comma-separated list of the session user’s role that can manage groups. Use tenable-sc-list-groups to get all available groups. | Optional |
| group_id | Valid group ID whose users can be managed by the 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 | Whether the user has manage permissions. |
| TenableSC.User.CanUse | Boolean | Whether the user has use 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 | Number of failed user 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 | Whether user is locked. |
| 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 role 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. |
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
Update user details of the 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 |
| 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 | Whether the user should be locked. 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. Only an Administrator can create Administrator accounts. Possible values are: Administrator, Security Manager, Security Analyst, Vulnerability Analyst, Executive, Credential Manager, Auditor. | Optional |
| must_change_password | Whether the password must be changed. 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 that can manage groups. Use tenable-sc-list-groups to get all available groups. | Optional |
| managed_objects_groups | Comma-separated list of session user’s role that can manage groups. Use tenable-sc-list-groups to get all available groups. | Optional |
| group_id | Valid group ID whose users can be managed by the created user. | Optional |
| responsible_asset_id | 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 the admin/Security Manager password from the instance parameters. Required when attempting to change a user’s password. | Optional |
| user_id | The ID of the user whose details you want 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 | Whether the user has manage permissions. |
| TenableSC.User.CanUse | Boolean | Whether the user has use 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 | Number of failed user 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 | Whether user is locked. |
| 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 role 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. |
Human Readable Output
user 23 was updated successfully
| User type | User Id | User Status | User Name | First Name | Lat Name | User Role Name | User Group Name | |
|---|---|---|---|---|---|---|---|---|
| regular | 23 | 0 | testuser30 | testuser30 | testuser30 | testuser30@mymail.com | Credential Manager | Full Access |
tenable-sc-delete-user
Delete a user by given user_id. This command can be executed with both roles (admin or security manager).
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.
Human Readable Output
User {user_id} was deleted successfully.
tenable-sc-list-plugin-family
List plugin families / return information about a plugin family given ID. Requires security manager role.
Base Command
tenable-sc-list-plugin-family
Input
| Argument Name | Description | Required |
|---|---|---|
| plugin_id | The ID of the plugin to search. If given, other arguments will be ignored. | Optional |
| limit | 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 | Plugin family ID. |
| TenableSC.PluginFamily.Name | String | Plugin family name. |
| TenableSC.PluginFamily.Count | String | Number of plugins in a family. |
| TenableSC.PluginFamily.Plugins | String | The plugins list. |
| TenableSC.PluginFamily.Type | String | Plugin family type. |
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
Creates a policy. Requires security manager role. This command is prerequisite for creating remediation scan.
Base Command
tenable-sc-create-policy
Input
| Argument Name | Description | Required |
|---|---|---|
| policy_name | The name of the policy to create. | Optional |
| policy_description | The description of the policy to create. | Optional |
| policy_template_id | Policy template id. Default is 1. 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 option. 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 | Family ID. Can be retrieved from the result of the tenable-sc-list-plugin-family command. | Required |
| plugins_id | Comma-separated list of plugin_ids, Can be retrieved from the result of the tenable-sc-list-plugin-family command with family_id as the argument. | Required |
| syn_firewall_detection | 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 username. |
| 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. |
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 | {policy UUID} | true | yuv | Advanced Scan |
tenable-sc-list-query
Lists the queries. Requires security manager role.
Base Command
tenable-sc-list-query
Input
| Argument Name | Description | Required |
|---|---|---|
| query_id | The ID of the query 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. |
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 Id | Query Name | Query Description |
|---|---|---|
| test_id | test_name | test_description |
tenable-sc-update-asset
Requires security manager role. 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 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.
Human Readable Output
asset {asset_id} was updated successfully.
tenable-sc-create-remediation-scan
Creates a remediation scan. Requires security manager role. This command is a prerequisite for creating remediation scan.
Base Command
tenable-sc-create-remediation-scan
Input
| Argument Name | Description | Required |
|---|---|---|
| policy_name | The name of the policy to create. | Optional |
| policy_description | The description of the policy 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 the tenable-sc-list-plugin-family command. | Required |
| plugins_id | Comma-separated list of plugin_ids, Can be retrieved from the result of the tenable-sc-list-plugin-family command with family_id as the argument. | Required |
| scan_name | Scan name. | Required |
| description | Scan description. | Optional |
| repository_id | Scan Repository ID, can be retrieved from the 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, in the format of YYYY-MM-DD:HH:MM:SS or relative timestamp (i.e., now, 3 days). | Optional |
| repeat_rule_freq | Specifies 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. Possible values are: SU, MO, TU, WE, TH, FR, SA. | Optional |
| asset_ids | Either no assets or comma-separated list of asset IDs to scan. Can be retrieved from the list-assets command. | Optional |
| scan_virtual_hosts | Default is false. Whether to include virtual hosts. 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 the list-report-definitions command. | Optional |
| credentials | Comma-separated credentials IDs to use. Can be retrieved from the list-credentials command. | Optional |
| timeout_action | 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 | Track hosts which have been issued new IP address, (e.g., DHCP). Possible values are: true, false. | Optional |
| enabled | Whether the schedule is 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 | 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 username. |
| 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 username. |
| 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 username. |
| 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. |
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 |
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.
tenable-sc-get-organization
Requires administrator role. Command to get a list of organizations’ information, depending on the comma-separated list of fields provided.
Base Command
tenable-sc-get-organization
Input
| Argument Name | Description | Required |
|---|---|---|
| fields | Optional fields to return specific values, example: restrictedIPs. | Optional |
Context Output
There is no context output for this command.
Configuration parameters
server— Server URL (e.g. https://192.168.0.1) (required)creds_keys— Access keycredentials— Usernameunsecure— Trust any certificate (not secure)proxy— Use system proxy settingsisFetch— Fetch incidentsincidentType— Incident typefetch_time— First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days)incidentFetchInterval— Incidents Fetch IntervalisFetchAssets— Fetch assets and vulnerabilitiesassetsFetchInterval— Assets fetch interval in minutes.
Commands (33)
-
tenable-sc-create-assetRequires security manager role. Create an asset in Tenable.sc with provided IP addresses.
-
tenable-sc-create-policyCreates a policy. Requires security manager role. This command is prerequisite for creating remediation scan.
-
tenable-sc-create-remediation-scanCreates a remediation scan. Requires security manager role. This command is a prerequisite for creating remediation scan.
-
tenable-sc-create-scanRequires security manager role. Create a scan on Tenable.sc
-
tenable-sc-create-userCreates a new user. This command can be executed with both roles (admin or security manager) based on the role_id you choose.
-
tenable-sc-delete-assetRequires security manager role. Delete the asset with the given ID from Tenable.sc.
-
tenable-sc-delete-scanRequires security manager role. Delete a scan in Tenable.sc.
-
tenable-sc-delete-userDelete a user by given user_id. This command can be executed with both roles (admin or security manager).
-
tenable-sc-get-alertRequires security manager role. Get information about a given alert in Tenable.sc.
-
tenable-sc-get-all-scan-resultsReturns all scan results in Tenable.sc. Requires security manager role.
-
tenable-sc-get-assetRequires security manager role. Get details for a given asset in Tenable.sc.
-
tenable-sc-get-deviceRequires security manager role. Gets the specified device information.
-
tenable-sc-get-organizationRequires administrator role. Command to get a list of organizations' information, depending on the comma-separated list of fields provided.
-
tenable-sc-get-scan-reportRequires security manager role. Get a single report with Tenable.sc scan results. In case of `Importstatus = Error` (The results import wasn't finished), the vulnerabilities section will not be added to the results.
-
tenable-sc-get-scan-statusRequires security manager role. Get the status of a specific scan in Tenable.sc.
-
tenable-sc-get-system-informationGet the system information and diagnostics from Tenable.sc. Requires admin role.
-
tenable-sc-get-system-licensingRetrieve licensing information from Tenable.sc. Requires admin role.
-
tenable-sc-get-vulnerabilityRequires security manager role. Get details about a given vulnerability from a given Tenable.sc scan.
-
tenable-sc-launch-scanRequires security manager role. Launch an existing scan from Tenable.sc. Set polling to true to follow the scan and receive results when scan is over.
-
tenable-sc-list-alertsRequires security manager role. List alerts from Tenable.sc.
-
tenable-sc-list-assetsRequires security manager role. Get a list of Tenable.sc assets.
-
tenable-sc-list-credentialsRequires security manager role. Get a list of Tenable.sc credentials.
-
tenable-sc-list-groupsList all groups. Requires security manager role.
-
tenable-sc-list-plugin-familyList plugin families / return information about a plugin family given ID. Requires security manager role.
-
tenable-sc-list-policiesRequires security manager role. Get a list of Tenable.sc scan policies.
-
tenable-sc-list-queryLists the queries. Requires security manager role.
-
tenable-sc-list-report-definitionsRequires security manager role. Get a list of Tenable.sc report definitions.
-
tenable-sc-list-repositoriesRequires security manager role. Get a list of Tenable.sc scan repositories.
-
tenable-sc-list-scansRequires security manager role. Get a list of Tenable.sc existing scans.
-
tenable-sc-list-usersList users in Tenable.sc. Results may vary based on the role type (admin or security manager).
-
tenable-sc-list-zonesRequires admin role. Get a list of Tenable.sc scan zones.
-
tenable-sc-update-assetRequires security manager role. Update an asset.
-
tenable-sc-update-userUpdate user details of the given user_id.
import functools import json import re import sys import time from datetime import datetime from typing import Any import demistomock as demisto # noqa: F401 import pytz import urllib3 from CommonServerPython import * # noqa: F401 from requests import Session, cookies # disable insecure warnings urllib3.disable_warnings() """ GLOBAL VARIABLES""" ACTION_TYPE_TO_VALUE = { "notification": "users.username", "email": "users.username", "syslog": "host", "scan": "scan.name", "report": "report.name", "ticket": "assignee.username", } FIELDS_TO_INCLUDE = "id,name,description,type,ownerGroup,owner,tags,modifiedTime,restrictedIPs" API_KEY = "API_KEY" USERNAME_AND_PASSWORD = "USERNAME_AND_PASSWORD" ROLE_ID_DICT = { "Administrator": "1", "Security Manager": "2", "Security Analyst": "3", "Vulnerability Analyst": "4", "Executive": "5", "Credential Manager": "6", "Auditor": "7", } # Asset and vulnerability fetch constants VENDOR = "tenable" PRODUCT = "sc" ASSETS_PAGE_SIZE = 500 VULNS_PAGE_SIZE = 500 MIN_ASSETS_INTERVAL = 60 # Minimum minutes between full fetch cycles XSIAM_EVENT_CHUNK_SIZE_LIMIT = 4 * (10**6) # 4 MB HOST_FIELDS = ( "id,uuid,tenableUUID,name,ipAddress,os,firstSeen,lastSeen," "macAddress,source,netBios,dns,acr,aes,repository,systemType," "createdTime,modifiedTime" ) 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: 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 self.max_retries = 3 self.headers: dict[str, Any] = {"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: 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_api_key_auth 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_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. 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_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: 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 headers["X-SecurityCenter"] = self.token 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_username_and_password_auth(path, method, body, params, headers, try_number + 1) elif res.status_code < 200 or res.status_code >= 300: try: error = res.json() except Exception: raise DemistoException( f"Error: Got status code {res.status_code!s} with {url=} with" f" body {res.content} with headers {res.headers!s}" # type: ignore[str-bytes-safe] ) raise DemistoException(f"Error: Got an error from TenableSC, code: {error['error_code']}, \ details: {error['error_msg']}") # type: ignore return res.json() def login(self): """ Set the token for username & password authentication method. """ login_body = {"username": self.user_name, "password": self.password} login_response = self.send_login_request(login_body) if "response" not in login_response: 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 # 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"]: raise DemistoException("Error: Could not retrieve login token") token = login_response["response"]["token"] self.token = str(token) 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 = {"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: raise DemistoException(f"Error: Got status code {res.status_code!s} with {url=} \ with body {res.content} with headers {res.headers!s}") # type: ignore[str-bytes-safe] self.cookie = res.cookies.get("TNS_SESSIONID", self.cookie) demisto.setIntegrationContext({"cookie": self.cookie}) return res.json() def __exit__(self, *args): """ Send the request to logout for username & password authentication method. """ if self.auth_method == USERNAME_AND_PASSWORD: self.send_request(path="token", method="DELETE") def create_scan(self, args: dict[str, Any]): """ Send the request for create_scan_command and create_remediation_scan_command. Args: args (dict): The demisto.args() object. 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): """ 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", "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 = 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} if policy_id := args.get("policy_id"): body["policy"] = {"id": policy_id} if zone_id := args.get("zone_id"): body["zone"] = {"id": zone_id} if report_ids := args.get("report_ids"): body["reports"] = [{"id": r_id, "reportSource": "individual"} for r_id in argToList(report_ids)] if asset_ids := args.get("asset_ids"): if str(asset_ids).startswith("All"): manageable = asset_ids == "AllManageable" res = self.get_assets(None) assets = get_elements(res["response"], manageable) asset_ids = [a["id"] for a in assets] body["assets"] = [{"id": a_id} for a_id in argToList(asset_ids)] if credentials := args.get("credentials"): body["credentials"] = [{"id": c_id} for c_id in argToList(credentials)] if max_scan_time := int(args.get("max_scan_time", "1")): body["maxScanTime"] = max_scan_time * 3600 if schedule := args.get("schedule"): schedule_body = {"type": schedule} if dependent := args.get("dependent_id"): schedule_body["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: 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)[0] if time_zone := args.get("time_zone") and 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]): 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: 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." ) schedule_body["enabled"] = argToBoolean(args.get("enabled", True)) body["schedule"] = schedule_body remove_nulls_from_dictionary(body) return 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: body = {"diagnosticTarget": scan_target["address"], "diagnosticPassword": scan_target["password"]} 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: params["type"] = 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,importStatus" } 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] if alert_id: path += "/" + alert_id if fields: params = {"fields": fields} return self.send_request(path, params=params) def get_organization(self, fields=None): """ Send the request for get_organization_command. Args: fields (str): The fields to include in the response. Returns: Dict: The response. """ params = {} # type: Dict[str, Any] if fields: params = {"fields": ",".join(fields)} return self.send_request(path="organization", 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: params = {"fields": 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: params = {"fields": 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: params = {"fields": fields} return self.send_request(path="asset", params=params) def get_credentials(self): """ Send the request for list_credentials_command. Returns: Dict: The response. """ params = {"fields": FIELDS_TO_INCLUDE} 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"} 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, "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): """ 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: params = {"fields": 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 = { "fields": "name,description,details,status,scannedIPs,progress,startTime,scanDuration,importStart," "finishTime,completedChecks,owner,ownerGroup,repository,policy,importStatus,running" } return self.send_request(path, params=params) 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 = {"name": f"scan {scan_id} query", "type": "vuln", "tool": tool, "scanID": scan_id} 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: raise DemistoException("query id returned None") path = "query/" + str(query_id) self.send_request(path, method="DELETE") def get_analysis(self, body=None, args={}): """ Send the request for get_vulnerability_command and get_vulnerabilities. Args: args (dict): Either an empty dict if passed from get_vulnerabilities, otherwise, the demisto.results() object. body (str): The request body (if function is called from get_vulnerabilities). Returns: Dict: The response. """ body = body or self.create_get_vulnerability_request_body(args) return self.send_request(path="analysis", method="POST", body=body) def create_get_vulnerability_request_body(self, args={}): """ Create the body for the request made in get_analysis. 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 = args.get("scan_results_id") sort_field = args.get("sort_field", "severity") 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")) limit = int(args.get("limit", "50")) if limit > 200: limit = 200 body = { "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 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.") 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: raise DemistoException(f"When choosing source_type = {source_type} - query_id must be provided.") body["query"] = query return body 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 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" 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 = { "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): """ 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, params = create_get_device_request_params_and_path(uuid, ip, dns_name, repo) return self.send_request(path, params=params) 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: 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: path += "/" + user_id params = None if fields: params = {"fields": fields} 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}" 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, args): """ Send the request for create_policy_command. Args: args (Dict): the demisto.args() object. Returns: Dict: The response. """ body = create_policy_request_body(args) return self.send_request(path="policy", method="POST", body=body) def fetch_vulnerabilities_analysis( self, start_offset: int = 0, end_offset: int = 500, ) -> dict: """ Fetch cumulative vulnerabilities via the analysis API for XSIAM ingestion. Uses sourceType=cumulative to get all known vulnerabilities across all scans. Args: start_offset: Starting offset for pagination. end_offset: Ending offset for pagination. Returns: Dict: The API response containing vulnerability analysis results. """ body = { "type": "vuln", "sourceType": "cumulative", "view": "all", "wasVuln": "excludeWas", "startOffset": start_offset, "endOffset": end_offset, "sortField": "severity", "sortDir": "DESC", "tool": "vulndetails", "query": { "type": "vuln", "tool": "vulndetails", "filters": [], }, } return self.send_request(path="analysis", method="POST", body=body) def search_hosts( self, fields: str, start_offset: int = 0, end_offset: int = 500, filters: dict | None = None, ) -> dict: """ Search hosts using the /hosts/search endpoint with pagination. Args: fields: Comma-separated list of fields to return. start_offset: Starting offset for pagination. end_offset: Ending offset for pagination. filters: Optional filter payload for the request body. Returns: Dict: The API response containing totalRecords, returnedRecords, and results. """ params = { "fields": fields, "startOffset": str(start_offset), "endOffset": str(end_offset), "pagination": "true", } body = filters or {} return self.send_request(path="hosts/search", method="POST", body=body, params=params) """ HELPER FUNCTIONS """ 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: 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"), "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: args (Dict): the demisto.args() object. Returns: Dict: The request body. """ 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", "emailNotice": "email_notice", "phone": "phone", "locked": "locked", "mustChangePassword": "must_change_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", ""): body["roleID"] = ROLE_ID_DICT.get(role_id, "") 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"] = [{"name": "timezone", "value": time_zone, "tag": ""}] return body def get_server_url(url): """ Remove redundant '/' from the url the server url. For example: www.example.com/ - > www.example.com. Args: url (str): The server url. Returns: str: The server url. """ url = re.sub(r"/[\/]+$/", "", url) url = re.sub(r"\/$", "", url) return url 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: 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") 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: raise DemistoException(f"{number_arg} must be a valid number.") 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" ) 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"): raise DemistoException("current_password must be provided when attempting to update password.") if len(password) < 3: 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 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.") 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") return 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 """ FUNCTIONS """ 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() if not (res and res.get("response")): raise DemistoException("No scans found") scans_dicts = get_elements(res["response"], manageable) if len(scans_dicts) == 0: raise DemistoException("No scans found") headers = ["ID", "Name", "Description", "Policy", "Group", "Owner"] mapped_scans = [ { "Name": s["name"], "ID": s["id"], "Description": s["description"], "Policy": s["policy"].get("name"), "Group": s["ownerGroup"].get("name"), "Owner": s["owner"].get("username"), } for s in scans_dicts ] 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]): """ 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() if not (res and res.get("response")): raise DemistoException("No policies found") policies = get_elements(res["response"], manageable) if len(policies) == 0: raise DemistoException("No policies found") headers = ["ID", "Name", "Description", "Tag", "Type", "Group", "Owner", "LastModified"] mapped_policies = [ { "ID": p["id"], "Name": p["name"], "Description": p["description"], "Tag": p["tags"], "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 ] 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]): """ 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 and res.get("response")): raise DemistoException("No repositories found") repositories = res["response"] if len(repositories) == 0: raise DemistoException("No repositories found") headers = ["ID", "Name", "Description"] mapped_repositories = [{"ID": r["id"], "Name": r["name"], "Description": r["description"]} for r in repositories] 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]): """ 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() manageable = args.get("manageable", "false").lower() if not (res and res.get("response")): raise DemistoException("No credentials found") credentials = get_elements(res["response"], manageable) if len(credentials) == 0: raise DemistoException("No credentials found") headers = ["ID", "Name", "Description", "Type", "Tag", "Group", "Owner", "LastModified"] mapped_credentials = [ { "ID": c["id"], "Name": c["name"], "Description": c["description"], "Type": c["type"], "Tag": c["tags"], "Group": c.get("ownerGroup", {}).get("name"), "Owner": c.get("owner", {}).get("name"), "LastModified": timestamp_to_utc(c["modifiedTime"]), } for c in credentials ] 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]): """ 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() if not (res and res.get("response")): raise DemistoException("No assets found") assets = get_elements(res["response"], manageable) if len(assets) == 0: raise DemistoException("No assets found") headers = ["ID", "Name", "Tag", "Owner", "Group", "Type", "HostCount", "LastModified"] mapped_assets = [ { "ID": a["id"], "Name": a["name"], "Tag": a["tags"], "Owner": a.get("owner", {}).get("username"), "Type": a["type"], "Group": a.get("ownerGroup", {}).get("name"), "HostCount": a["ipCount"], "LastModified": timestamp_to_utc(a["modifiedTime"]), } for a in assets ] 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]): """ 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) if not res or "response" not in res: raise DemistoException("Asset not found") asset = res["response"] ips = [] # type: List[str] ip_lists = [v["ipList"] for v in asset.get("viewableIPs", "")] for ip_list in ip_lists: # Extract IPs ips += re.findall(r"[0-9]+(?:\.[0-9]+){3}", ip_list) headers = ["ID", "Name", "Description", "Tag", "Created", "Modified", "Owner", "Group", "IPs"] mapped_asset = { "ID": asset["id"], "Name": asset["name"], "Description": asset["description"], "Tag": asset["tags"], "Created": timestamp_to_utc(asset["createdTime"]), "Modified": timestamp_to_utc(asset["modifiedTime"]), "Owner": asset.get("owner", {}).get("username"), "Group": asset.get("ownerGroup", {}).get("name"), "IPs": ips, } 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]): """ 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") tags = args.get("tags") ips = args.get("ip_list") res = client.create_asset(name, description, owner_id, tags, ips) if not res or "response" not in res: raise DemistoException("Error: Could not retrieve the asset") asset = res["response"] mapped_asset = { "ID": asset["id"], "Name": asset["name"], "OwnerName": asset["owner"].get("username"), "Tags": asset["tags"], } headers = ["ID", "Name", "OwnerName", "Tags"] 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]): """ 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) if not res: raise DemistoException("Error: Could not delete the asset") return CommandResults(raw_response=res, readable_output=f"Asset {asset_id} was deleted successfully.") def list_report_definitions_command(client: Client, args: dict[str, Any]): """ Lists report definitions. 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() if not (res and res.get("response")): raise DemistoException("No report definitions found") reports = get_elements(res["response"], manageable) # Remove duplicates, take latest reports = [ functools.reduce( lambda x, y: x if int(x["modifiedTime"]) > int(y["modifiedTime"]) else y, filter(lambda e: e["name"] == n, reports) ) for n in {r["name"] for r in reports} ] if len(reports) == 0: raise DemistoException("No report definitions found") headers = ["ID", "Name", "Description", "Type", "Group", "Owner"] mapped_reports = [ { "ID": r["id"], "Name": r["name"], "Description": r["description"], "Type": r["type"], "Group": r.get("ownerGroup", {}).get("name"), "Owner": r.get("owner", {}).get("username"), } for r in reports ] hr = tableToMarkdown("Tenable.sc Report Definitions", mapped_reports, headers, removeNull=True) for r in mapped_reports: del r["Description"] 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]): """ 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: raise DemistoException("No zones found") zones = res["response"] if len(zones) == 0: zones = [{"id": 0, "name": "All Zones", "description": "", "ipList": "", "activeScanners": ""}] headers = ["ID", "Name", "Description", "IPList", "activeScanners"] mapped_zones = [ { "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, found_ids = [], [] for index, zone in enumerate(zones): if scanners := zone.get("scanners"): mapped_scanners = [ {"ID": scanner["id"], "Name": scanner["name"], "Description": scanner["description"], "Status": scanner["status"]} for scanner in scanners ] mapped_zones[index]["Scanner"] = 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: hr += tableToMarkdown("Tenable.sc Scanners", mapped_scanners_total, headers, 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): """ 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", []) return elements.get("manageable", []) def create_scan_command(client: Client, args: dict[str, Any]): """ 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. """ validate_create_scan_inputs(args) res = client.create_scan(args) if not res or "response" not in res: raise DemistoException("Error: Could not retrieve the scan") scan = res["response"] headers = ["ID", "CreatorID", "Name", "Type", "CreationTime", "OwnerName", "Reports"] mapped_scan = { "ID": scan["id"], "CreatorID": scan["creator"].get("id"), "Name": scan["name"], "Type": scan["type"], "CreationTime": timestamp_to_utc(scan["createdTime"]), "OwnerName": scan["owner"].get("name"), "Reports": demisto.dt(scan["reports"], "id"), } 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 validate_create_scan_inputs(args: dict[str, Any]): """ 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: 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: raise DemistoException("Error: Assets and/or IPs must be provided") if schedule == "dependent" and not dependent: raise DemistoException("Error: Dependent schedule must include a dependent scan ID") def process_launch_scan_response(res: dict[str, Any]): """ Process the launch scan response. Args: res (Dict): the launch scan response. Returns: CommandResults: command results object with the response, human readable section, and the context entries to add. """ scan_result = res["response"]["scanResult"] headers = ["Name", "ID", "OwnerID", "JobID", "Status"] mapped_scan = { "Name": scan_result["name"], "ID": scan_result["id"], "OwnerID": scan_result["ownerID"], "JobID": scan_result["jobID"], "Status": scan_result["status"], } 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( name="tenable-sc-launch-scan", requires_polling_arg=True, poll_message="Scan in progress.", timeout=arg_to_number(demisto.args().get("timeout_in_seconds", "10800")), ) def launch_scan_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) if not argToBoolean(args.get("polling", "false")): return PollResult(process_launch_scan_response(res)) 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) scan_status = scan_results[0].get("status") if scan_status == "Error": raise DemistoException(f"Encountered the following error during the execution {scan_results[0].get('errorDetails')}") elif scan_status != "Completed": return PollResult(continue_to_poll=True, response=scan_results, args_for_next_run=args) else: return PollResult(get_scan_report_command(client, args)) 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") if (target_address and not target_password) or (target_password and not target_address): 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.") return res 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", "Error"] mapped_scans_results = [ { "ID": scan_result["id"], "Name": scan_result["name"], "Status": scan_result["status"], "Description": scan_result["description"], "Error": scan_result["errorDetails"] if scan_result["status"] == "Error" else "", } for scan_result in scans_results ] 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]): """ 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 = [] for scan_results_id in scan_results_ids: res = client.get_scan_results(scan_results_id) if not (res and res.get("response")): raise DemistoException("Scan results not found") scans_results.append(res["response"]) return scans_results, res 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", [])) res = client.get_scan_report(scan_results_id) if not (res and res.get("response")): raise DemistoException("Scan results not found") scan_results = res["response"] headers = [ "ID", "Name", "Description", "Policy", "Group", "Owner", "ScannedIPs", "StartTime", "EndTime", "Duration", "Checks", "ImportTime", "RepositoryName", "Status", "Scan Type", "ImportStatus", "IsScanRunning", "CompletedIPs", ] vuln_headers = ["ID", "Name", "Family", "Severity", "Total"] mapped_results = { "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.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", ""), "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 and scan_results.get("importStatus", "") != "Error": vulns = get_vulnerabilities(client, scan_results_id) if isinstance(vulns, list): vulnerabilities = list(filter(lambda v: v["Severity"] in vulnerabilities_to_get, vulns)) if vulnerabilities and len(vulnerabilities) > 0: hr += tableToMarkdown("Vulnerabilities", vulnerabilities, vuln_headers, removeNull=True) mapped_results["Vulnerability"] = vulnerabilities return CommandResults( outputs=createContext(mapped_results, removeNull=True), outputs_prefix="TenableSC.ScanResults", raw_response=res, outputs_key_field="ID", readable_output=hr, ) 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: return "Could not get vulnerabilites query" 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")) if not analysis or "response" not in analysis: return "Could not get vulnerabilites analysis" results = analysis["response"]["results"] if not results or len(results) == 0: return "No vulnerabilities found" mapped_vulns = [] for vuln in results: mapped_vuln = { "ID": vuln["pluginID"], "Name": vuln["name"], "Description": vuln["pluginDescription"], "Family": vuln["family"].get("name"), "Severity": vuln["severity"].get("name"), "Total": vuln["total"], } mapped_vulns.append(mapped_vuln) sv_level = {"Critical": 4, "High": 3, "Medium": 2, "Low": 1, "Info": 0} mapped_vulns.sort(key=lambda r: sv_level[r["Severity"]]) return mapped_vulns 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") analysis = client.get_analysis(args=args) if not analysis or "response" not in analysis: raise DemistoException("Error: Could not get vulnerability analysis") results = analysis["response"]["results"] if not results or len(results) == 0: 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: raise DemistoException("Vulnerability not found") vuln = vuln_response["response"] vuln["severity"] = results[0]["severity"] # The vulnerability severity is the same in all the results hosts = get_vulnerability_hosts_from_analysis(results) cves = None cves_output = [] # type: List[dict] if vuln.get("xrefs"): # Extract CVE cve_filter = list(filter(lambda x: x.strip().startswith("CVE"), vuln["xrefs"].split(","))) if cve_filter and len(cve_filter) > 0: cves = [c.replace("CVE:", "").strip() for c in cve_filter] cves_output += ({"ID": c} for c in cves) mapped_vuln = { "ID": vuln["id"], "Name": vuln["name"], "Description": vuln["description"], "Type": vuln["type"], "Severity": vuln.get("severity", {}).get("name"), "Synopsis": vuln["synopsis"], "Solution": vuln["solution"], } vuln_info = {"Published": timestamp_to_utc(vuln["vulnPubDate"]), "CPE": vuln["cpe"], "CVE": cves} exploit_info = {"ExploitAvailable": vuln["exploitAvailable"], "ExploitEase": vuln["exploitEase"]} risk_info = { "RiskFactor": vuln["riskFactor"], "CVSSBaseScore": vuln["baseScore"], "CVSSTemporalScore": vuln["temporalScore"], "CVSSVector": vuln["cvssVector"], } plugin_details = { "Family": vuln["family"].get("name"), "Published": timestamp_to_utc(vuln["pluginPubDate"]), "Modified": timestamp_to_utc(vuln["pluginModDate"]), "CheckType": vuln["checkType"], } hr = "## Vulnerability: {} ({})\n".format(mapped_vuln["Name"], mapped_vuln["ID"]) hr += "### Synopsis\n{}\n### Description\n{}\n### Solution\n{}\n".format( mapped_vuln["Synopsis"], mapped_vuln["Description"], mapped_vuln["Solution"] ) hr += tableToMarkdown("Hosts", hosts, removeNull=True) hr += tableToMarkdown("Risk Information", risk_info, removeNull=True) hr += tableToMarkdown("Exploit Information", exploit_info, removeNull=True) hr += tableToMarkdown("Plugin Details", plugin_details, removeNull=True) hr += tableToMarkdown("Vulnerability Information", vuln_info, removeNull=True) mapped_vuln.update(vuln_info) mapped_vuln.update(exploit_info) mapped_vuln.update(risk_info) mapped_vuln["PluginDetails"] = plugin_details mapped_vuln["Host"] = hosts scan_result = { "ID": scan_results_id, "Vulnerability": mapped_vuln, } 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: command_results.append(CommandResults(outputs=createContext(cves_output), outputs_prefix="CVE", outputs_key_field="ID")) 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.get("ip"), "MAC": host.get("macAddress"), "Port": host.get("port"), "Protocol": host.get("protocol")} for host in 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) if not res: raise DemistoException("Error: Could not delete the scan") return CommandResults(raw_response=res, readable_output=f"Scan {scan_id} was deleted successfully.") 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") repo = args.get("repository_id") res = client.get_device(uuid, ip, dns_name, repo) if not res or "response" not in res: raise DemistoException("Device not found") device = res["response"] headers = [ "IP", "UUID", "MacAddress", "RepositoryID", "RepositoryName", "NetbiosName", "DNSName", "OS", "OsCPE", "LastScan", "TotalScore", "LowSeverity", "MediumSeverity", "HighSeverity", "CriticalSeverity", ] mapped_device = { "IP": device["ip"], "UUID": device.get("uuid"), "MacAddress": device.get("macAddress"), "RepositoryID": device.get("repository", {}).get("id"), "RepositoryName": device.get("repository", {}).get("name"), "NetbiosName": device.get("netbiosName"), "DNSName": device.get("dnsName"), "OS": re.sub("<[^<]+?>", " ", device["os"]).lstrip() if device.get("os") else "", "OsCPE": device.get("osCPE"), "LastScan": timestamp_to_utc(device.get("lastScan")), "TotalScore": device.get("total"), "LowSeverity": device.get("severityLow"), "MediumSeverity": device.get("severityMedium"), "HighSeverity": device.get("severityHigh"), "CriticalSeverity": device.get("severityCritical"), } endpoint = { "IPAddress": mapped_device["IP"], "MACAddress": mapped_device["MacAddress"], "Hostname": mapped_device["DNSName"], "OS": mapped_device["OS"], } 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") res = client.get_users("id,username,firstname,lastname,title,email,createdTime,modifiedTime,lastLogin,role", user_id) if not res or "response" not in res: raise DemistoException("No users found") users = res["response"] if not isinstance(users, list): users = [users] if not user_id: if username: users = list(filter(lambda u: u["username"] == username, users)) elif email: users = list(filter(lambda u: u["email"] == email, users)) if len(users) == 0: raise DemistoException("No users found") headers = ["ID", "Username", "Firstname", "Lastname", "Title", "Email", "Created", "Modified", "LastLogin", "Role"] mapped_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), 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]): """ 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: raise DemistoException("Error: Could not retrieve system licensing") status = res["response"] mapped_licensing = { "License": status["licenseStatus"], "LicensedIPS": status["licensedIPs"], "ActiveIPS": status["activeIPs"], } headers = ["License", "LicensedIPS", "ActiveIPS"] 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]): """ 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: raise DemistoException("Error: Could not retrieve system information") diag_res = client.get_system_diagnostics() if not diag_res or "response" not in diag_res: raise DemistoException("Error: Could not retrieve system information") sys_res.update(diag_res) diagnostics = diag_res["response"] system = sys_res["response"] mapped_information = { "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", ] 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]): """ 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() if not (res and res.get("response")): raise DemistoException("No alerts found") alerts = get_elements(res["response"], manageable) if len(alerts) == 0: raise DemistoException("No alerts found") headers = ["ID", "Name", "Actions", "State", "LastTriggered", "LastEvaluated", "Group", "Owner"] mapped_alerts = [ { "ID": a["id"], "Name": a["name"], "State": "Triggered" if a["didTriggerLastEvaluation"] == "true" else "Not Triggered", "Actions": demisto.dt(a["action"], "type"), "LastTriggered": timestamp_to_utc(a["lastTriggered"], default_returned_value="Never"), "LastEvaluated": timestamp_to_utc(a["lastEvaluated"]), "Group": a["ownerGroup"].get("name"), "Owner": a["owner"].get("username"), } for a in alerts ] 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]): """ 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) if not (res and res.get("response")): raise DemistoException("Alert not found") alert = res["response"] query_res = client.get_query(alert.get("query", {}).get("id")) query = query_res.get("response") alert_headers = ["ID", "Name", "Description", "LastTriggered", "State", "Behavior", "Actions"] query_headers = ["Trigger", "Query"] action_headers = ["Type", "Values"] filter_headers = ["Name", "Values"] mapped_alert = { "ID": alert["id"], "Name": alert["name"], "Description": alert["description"], "LastTriggered": timestamp_to_utc(alert["lastTriggered"], default_returned_value="Never"), "State": "Triggered" if alert["didTriggerLastEvaluation"] == "true" else "Not Triggered", "Behavior": "Execute on every trigger " if alert["executeOnEveryTrigger"] == "true" else "Execute only on first trigger", } mapped_condition = { "Trigger": "{} {} {}".format(alert["triggerName"], alert["triggerOperator"], alert["triggerValue"]), "Query": alert["query"].get("name"), } mapped_filters = None if query: mapped_filters = [ {"Name": f["filterName"], "Values": demisto.dt(f["value"], "name") if isinstance(f["value"], list) else f["value"]} for f in query.get("filters", []) ] mapped_condition["Filter"] = mapped_filters mapped_actions = [ {"Type": a["type"], "Values": demisto.dt(a, "{}.{}".format("definition", ACTION_TYPE_TO_VALUE[a["type"]]))} for a in alert["action"] ] hr = tableToMarkdown("Tenable.sc Alert", mapped_alert, headers=alert_headers, removeNull=True) hr += tableToMarkdown("Condition", mapped_condition, headers=query_headers, removeNull=True) if mapped_filters: hr += tableToMarkdown("Filters", mapped_filters, headers=filter_headers, removeNull=True) if mapped_actions: hr += tableToMarkdown("Actions", mapped_actions, headers=action_headers, removeNull=True) mapped_alert["Action"] = mapped_actions mapped_alert["Condition"] = mapped_condition return CommandResults( outputs=createContext(mapped_alert, removeNull=True), outputs_prefix="TenableSC.Alert", raw_response=res, outputs_key_field="ID", readable_output=hr, ) def get_organization_command(client: Client, args: dict[str, Any]): """ Returns organization 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. """ fields = argToList(args.get("fields", None)) res = client.get_organization(fields) if not res or "response" not in res: raise DemistoException("Error: Could not retrieve organization information") response = res.get("response", {}) res_output = [] if response: for curr_res in response: restrictedIPMap = { "ID": curr_res.get("id", ""), "Name": curr_res.get("name", ""), } for field in fields: restrictedIPMap[field] = curr_res.get(field, "") res_output.append(restrictedIPMap) return CommandResults( outputs=createContext(response, removeNull=True), outputs_prefix="TenableSC.Organization", raw_response=res, outputs_key_field="ID", readable_output=tableToMarkdown("Tenable.sc Orgnization", res_output, removeNull=True), ) 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: last_run = {} if "time" not in last_run: # get timestamp in seconds timestamp, _ = parse_date_range(first_fetch, to_timestamp=True) timestamp /= 1000 else: timestamp = last_run["time"] max_timestamp = timestamp res = client.get_alerts( fields="id,name,description,lastTriggered,triggerName,triggerOperator," "triggerValue,action,query,owner,ownerGroup,schedule,canManage" ) alerts = get_elements(res.get("response", {}), manageable="false") for alert in alerts: # 0 corresponds to never triggered if int(alert.get("lastTriggered", 0)) > timestamp: incidents.append( { "name": "Tenable.sc Alert Triggered - " + alert["name"], "occurred": timestamp_to_utc(alert["lastTriggered"]), "rawJSON": json.dumps(alert), } ) if int(alert["lastTriggered"]) > max_timestamp: max_timestamp = int(alert["lastTriggered"]) demisto.incidents(incidents) demisto.setLastRun({"time": max_timestamp}) 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) if not res or not res.get("response", []): raise DemistoException("No groups found") groups = res.get("response", []) if len(groups) > limit: groups = groups[:limit] 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.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") hr += f"{tableToMarkdown(f'Group id:{group_id}', users, headers, removeNull=True)}\n" return CommandResults( outputs=createContext(response_to_context(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]): """ 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")) limit = int(args.get("limit", "50")) if limit > 200: limit = 200 if not (res and res.get("response")): raise DemistoException("Scan results not found") elements = get_elements(res["response"], get_manageable_results) headers = [ "ID", "Name", "Status", "Description", "Policy", "Group", "Owner", "ScannedIPs", "StartTime", "EndTime", "Duration", "Checks", "ImportTime", "RepositoryName", ] scan_results = [ { "ID": elem["id"], "Name": elem["name"], "Status": elem["status"], "Description": elem.get("description", None), "Policy": elem["details"], "Group": elem.get("ownerGroup", {}).get("name"), "Checks": elem.get("completedChecks", None), "StartTime": timestamp_to_utc(elem["startTime"]), "EndTime": timestamp_to_utc(elem["finishTime"]), "Duration": scan_duration_to_demisto_format(elem["scanDuration"]), "ImportTime": timestamp_to_utc(elem["importStart"]), "ScannedIPs": elem["scannedIPs"], "Owner": elem["owner"].get("username"), "RepositoryName": elem["repository"].get("name"), "ImportStatus": elem.get("importStatus", ""), } for elem in elements[page : page + limit] ] readable_title = f"Tenable.sc Scan results - {page}-{page + limit - 1}" hr = tableToMarkdown( readable_title, scan_results, headers, removeNull=True, metadata=f"Total number of elements is {len(elements)}" ) 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]): """ 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.' return process_update_and_create_user_response(res, hr_header) 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 successfully.' return 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", {}): 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", {}) 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"), } return CommandResults( outputs=createContext(response_to_context(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]): """ 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) return CommandResults(raw_response=res, readable_output=f"User {user_id} was deleted successfully.") 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", "") res = client.list_plugin_family(plugin_id, is_active) if not res or not res.get("response", []): raise DemistoException("No plugins found") plugins = res.get("response") if isinstance(plugins, dict): 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] 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"] return CommandResults( outputs=createContext(response_to_context(plugins), removeNull=True), 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]): """ 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"), "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("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 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 ID", "policyTemplate id", "policyTemplate Name", ] return CommandResults( outputs=createContext(response_to_context(created_policy), removeNull=True), outputs_prefix="TenableSC.ScanPolicy", raw_response=res, outputs_key_field="ID", readable_output=tableToMarkdown("Policy was created successfully:", mapped_created_policy, headers, removeNull=True), ) 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" args["scan_type"] = "policy" args["schedule"] = "now" 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: raise DemistoException("Error: Could not retrieve the scan") 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", ] 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(response_to_context(scan), removeNull=True), outputs_prefix="TenableSC.Scan", raw_response=res, outputs_key_field="ID", 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. 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") 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) return CommandResults( outputs=createContext(response_to_context(ec), removeNull=True), outputs_prefix="TenableSC.Query", raw_response=res, outputs_key_field="ID", readable_output=hr, ) 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", []): raise DemistoException("Couldn't update asset.") 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", []): raise DemistoException(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): """ 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", []): raise DemistoException("No queries found.") queries = res.get("response") manageable_queries = queries.get("manageable", []) usable_queries = queries.get("usable", []) 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", "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) return res, hr, queries """ FETCH ASSETS FUNCTIONS """ def generate_snapshot_id() -> str: """ Generate a unique snapshot ID for XSIAM dataset snapshots. Uses current timestamp in milliseconds to ensure uniqueness across fetch cycles. This ID is used to group assets that belong to the same snapshot, allowing XSIAM to properly track complete vs incomplete snapshots. Returns: str: A unique snapshot ID based on current timestamp in milliseconds. """ return str(round(time.time() * 1000)) def skip_fetch_assets(last_run: dict) -> bool: """ Check if enough time has passed since the last completed fetch cycle. Args: last_run: The last run object containing fetch state. Returns: bool: True if the fetch should be skipped (not enough time has passed). """ time_to_check = last_run.get("assets_last_fetch") if not time_to_check: return False # If there's an ongoing fetch (assets or vulns), don't skip if is_assets_fetch_in_progress(last_run) or is_vulns_fetch_in_progress(last_run): return False passed_minutes = (time.time() - time_to_check) / 60 to_skip = passed_minutes < MIN_ASSETS_INTERVAL if to_skip: demisto.info( f"Skipping fetch-assets command. Only {passed_minutes:.1f} minutes have passed since the last fetch. " f"Minimum interval is {MIN_ASSETS_INTERVAL} minutes." ) return to_skip def is_assets_fetch_in_progress(last_run: dict) -> bool: """ Check if an assets fetch is still in progress (has more pages to fetch). Args: last_run: The last run object containing fetch state. Returns: bool: True if there is an ongoing paginated fetch. """ return "current_offset" in last_run def fetch_assets_page(client: Client, last_run: dict) -> list: """ Fetch a single page of host assets from the Tenable.sc API. Args: client: Client class object. last_run: The last run object containing fetch state. Returns: list: List of host asset records from this page. """ current_offset = last_run.get("current_offset", 0) end_offset = current_offset + ASSETS_PAGE_SIZE demisto.debug(f"Fetching assets page: offset {current_offset} to {end_offset}") response = client.search_hosts( fields=HOST_FIELDS, start_offset=current_offset, end_offset=end_offset, ) if not response or "response" not in response: demisto.debug("No response received from hosts/search endpoint.") # Clear fetch state on error last_run.pop("current_offset", None) last_run.pop("total_records", None) return [] response_data = response["response"] # Handle both list response (simple) and dict response (paginated) if isinstance(response_data, list): results = response_data total_records = len(results) else: results = response_data.get("results", []) total_records = int(response_data.get("totalRecords", 0)) demisto.info(f"Received {len(results)} assets (offset {current_offset}, total {total_records})") # Update cumulative count cumulative = last_run.get("total_assets_fetched", 0) + len(results) last_run["total_assets_fetched"] = cumulative # Determine if there are more pages next_offset = current_offset + len(results) if next_offset < total_records and len(results) > 0: # More pages to fetch last_run["current_offset"] = next_offset last_run["total_records"] = total_records last_run["nextTrigger"] = "30" demisto.debug(f"More assets to fetch. Next offset: {next_offset}, total: {total_records}") else: # All pages fetched - clear pagination state last_run.pop("current_offset", None) last_run.pop("total_records", None) demisto.info(f"Asset fetch complete. Total assets fetched: {cumulative}") return results def fetch_assets_command(client: Client, last_run: dict) -> list: """ Fetch assets from the Tenable.sc API, handling pagination state. Args: client: Client class object. last_run: The last run object containing fetch state. Returns: list: Assets fetched from the API for this page. """ return fetch_assets_page(client, last_run) def run_assets_fetch(client: Client, last_run: dict) -> list: # pragma: no cover """ Entry point for running the assets fetch. Initializes fetch state if starting a new cycle, then fetches one page. Args: client: Client class object. last_run: The last run object containing fetch state. Returns: list: Assets fetched from the API. """ demisto.info("fetch assets from the API") # Starting new fetch cycle - initialize state if not is_assets_fetch_in_progress(last_run): snapshot_id = generate_snapshot_id() demisto.debug(f"Starting new asset fetch cycle with snapshot_id: {snapshot_id}") last_run.update( { "current_offset": 0, "total_assets_fetched": 0, "snapshot_id": snapshot_id, } ) return fetch_assets_command(client, last_run) """ FETCH VULNERABILITIES FUNCTIONS """ def is_vulns_fetch_in_progress(last_run: dict) -> bool: """ Check if a vulnerabilities fetch is still in progress (has more pages to fetch). Args: last_run: The last run object containing fetch state. Returns: bool: True if there is an ongoing paginated vulnerability fetch. """ return "vuln_current_offset" in last_run def fetch_vulnerabilities_page(client: Client, last_run: dict) -> list: """ Fetch a single page of cumulative vulnerabilities from the Tenable.sc Analysis API. Args: client: Client class object. last_run: The last run object containing fetch state. Returns: list: List of vulnerability records from this page. """ current_offset = last_run.get("vuln_current_offset", 0) end_offset = current_offset + VULNS_PAGE_SIZE demisto.debug(f"Fetching vulnerabilities page: offset {current_offset} to {end_offset}") response = client.fetch_vulnerabilities_analysis( start_offset=current_offset, end_offset=end_offset, ) if not response or "response" not in response: demisto.debug("No response received from analysis endpoint for vulnerabilities.") # Clear vuln fetch state on error last_run.pop("vuln_current_offset", None) last_run.pop("total_vulns_fetched", None) return [] response_data = response["response"] results = response_data.get("results", []) total_records = int(response_data.get("totalRecords", 0)) demisto.info(f"Received {len(results)} vulnerabilities (offset {current_offset}, total {total_records})") # Update cumulative count cumulative = last_run.get("total_vulns_fetched", 0) + len(results) last_run["total_vulns_fetched"] = cumulative # Determine if there are more pages next_offset = current_offset + len(results) if next_offset < total_records and len(results) > 0: # More pages to fetch last_run["vuln_current_offset"] = next_offset last_run["nextTrigger"] = "30" demisto.debug(f"More vulnerabilities to fetch. Next offset: {next_offset}, total: {total_records}") else: # All pages fetched - clear vuln pagination state last_run.pop("vuln_current_offset", None) demisto.info(f"Vulnerability fetch complete. Total vulnerabilities fetched: {cumulative}") return results def parse_vulnerabilities(vulns: list) -> list: """ Parse and prepare vulnerabilities for XSIAM ingestion. Adds _time field, truncates oversized entries, and marks truncation status. Mirrors the Tenable_io parse_vulnerabilities pattern. Args: vulns: List of raw vulnerability records from the analysis API. Returns: list: Parsed vulnerability records ready for XSIAM. """ demisto.debug("Parse the vulnerabilities...") if not isinstance(vulns, list): demisto.debug(f"result is of type: {type(vulns)}") vulns = list(vulns) for vuln in vulns: # Set _time from lastSeen or firstSeen (Tenable.sc uses these fields) vuln["_time"] = vuln.get("firstSeen") or vuln.get("lastSeen") vuln_str = json.dumps(vuln) if sys.getsizeof(vuln_str) > XSIAM_EVENT_CHUNK_SIZE_LIMIT: demisto.debug(f"found oversized vulnerability object: {sys.getsizeof(vuln_str)} bytes") if vuln.get("pluginText"): demisto.debug("truncating pluginText field") vuln["pluginText"] = "" vuln["isTruncated"] = True elif vuln.get("pluginDescription"): demisto.debug("truncating pluginDescription field") vuln["pluginDescription"] = "" vuln["isTruncated"] = True else: demisto.debug("skipping oversized object...") continue else: vuln["isTruncated"] = False return vulns def run_vulns_fetch(client: Client, last_run: dict) -> list: # pragma: no cover """ Entry point for running the vulnerabilities fetch. Initializes vuln fetch state if starting a new cycle, then fetches one page. Args: client: Client class object. last_run: The last run object containing fetch state. Returns: list: Vulnerabilities fetched from the API. """ demisto.info("fetch vulnerabilities from the API") # Starting new vuln fetch cycle - initialize state if not is_vulns_fetch_in_progress(last_run): demisto.debug("Starting new vulnerability fetch cycle") last_run.update( { "vuln_current_offset": 0, "total_vulns_fetched": 0, } ) return fetch_vulnerabilities_page(client, last_run) def test_module(client: Client, args: dict[str, Any]): """ Test the connection to the Tenable.sc server. Args: client (Client): The tenable.sc client object. args (Dict): demisto.args() object. Returns: str: 'ok' if the connection is successful. """ try: # Validate assets fetch interval if configured params = demisto.params() assets_fetch_interval = params.get("assetsFetchInterval") or MIN_ASSETS_INTERVAL if int(assets_fetch_interval) < MIN_ASSETS_INTERVAL: raise DemistoException(f"Assets fetch interval must be at least {MIN_ASSETS_INTERVAL} minutes (1 hour).") client.get_users() return "ok" except DemistoException: raise except Exception: raise Exception("Authorization Error: make sure your API Key and Secret Key are correctly set") def main(): # pragma: no cover 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") 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, "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-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-get-scan-status": get_scan_status_command, "tenable-sc-get-scan-report": get_scan_report_command, "tenable-sc-delete-scan": delete_scan_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, "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-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, "tenable-sc-get-organization": get_organization_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: if command == "fetch-incidents": first_fetch = params.get("fetch_time").strip() fetch_incidents(client, first_fetch) elif command == "fetch-assets": assets_last_run = demisto.getAssetsLastRun() demisto.debug(f"saved lastrun assets: {assets_last_run}") if skip_fetch_assets(assets_last_run): return # Mark the start of a new fetch cycle if not resuming if not is_assets_fetch_in_progress(assets_last_run) and not is_vulns_fetch_in_progress(assets_last_run): assets_last_run["assets_last_fetch"] = time.time() assets_last_run_copy = assets_last_run.copy() assets: list = [] vulnerabilities: list = [] # Fetch Assets: Run if assets fetch is in progress or starting new cycle (vulns not yet started) if is_assets_fetch_in_progress(assets_last_run_copy) or not is_vulns_fetch_in_progress(assets_last_run_copy): assets = run_assets_fetch(client, assets_last_run) # Fetch Vulnerabilities: Run if vulns fetch is in progress or assets fetch is complete if is_vulns_fetch_in_progress(assets_last_run_copy) or not is_assets_fetch_in_progress(assets_last_run): vulnerabilities = run_vulns_fetch(client, assets_last_run) demisto.info(f"Received {len(assets)} assets and {len(vulnerabilities)} vulnerabilities.") demisto.setAssetsLastRun(assets_last_run) # Get snapshot_id for this fetch cycle snapshot_id = assets_last_run.get("snapshot_id") if not snapshot_id: snapshot_id = generate_snapshot_id() assets_last_run["snapshot_id"] = snapshot_id demisto.setAssetsLastRun(assets_last_run) assets_fetch_in_progress = is_assets_fetch_in_progress(assets_last_run) if assets: cumulative_total = assets_last_run.get("total_assets_fetched", 0) # Per XSIAM spec: items_count=1 if not finished, else total count items_count = 1 if assets_fetch_in_progress else cumulative_total demisto.debug( f"Sending {len(assets)} assets to XSIAM with snapshot_id={snapshot_id}, " f"items_count={items_count}, cumulative_total={cumulative_total}, " f"assets_fetch_in_progress={assets_fetch_in_progress}" ) send_data_to_xsiam( data=assets, vendor=VENDOR, product=f"{PRODUCT}_assets", data_type="assets", snapshot_id=snapshot_id, items_count=str(items_count), should_update_health_module=False, add_proxy_to_request=proxy, ) elif not assets_fetch_in_progress: # Seal empty snapshot if we had previously sent data cumulative_total = assets_last_run.get("total_assets_fetched", 0) if cumulative_total > 0: demisto.debug( f"Asset fetch completed with empty assets list. Sealing snapshot with " f"snapshot_id={snapshot_id}, items_count={cumulative_total}" ) send_data_to_xsiam( data=[], vendor=VENDOR, product=f"{PRODUCT}_assets", data_type="assets", snapshot_id=snapshot_id, items_count=str(cumulative_total), should_update_health_module=False, add_proxy_to_request=proxy, ) # Send vulnerabilities to XSIAM if vulnerabilities: vulnerabilities = parse_vulnerabilities(vulnerabilities) demisto.debug(f"Sending {len(vulnerabilities)} vulnerabilities to XSIAM.") send_data_to_xsiam( data=vulnerabilities, vendor=VENDOR, product=f"{PRODUCT}_vulnerabilities", add_proxy_to_request=proxy, ) # Update module health if ( assets or vulnerabilities or (not assets_fetch_in_progress and not is_vulns_fetch_in_progress(assets_last_run)) ): cumulative_total = assets_last_run.get("total_assets_fetched", 0) demisto.updateModuleHealth({"assetsPulled": cumulative_total}) # Clean up snapshot state when BOTH assets and vulns fetch are complete if not assets_fetch_in_progress and not is_vulns_fetch_in_progress(assets_last_run): assets_last_run.pop("snapshot_id", None) assets_last_run.pop("total_assets_fetched", None) assets_last_run.pop("total_vulns_fetched", None) demisto.setAssetsLastRun(assets_last_run) demisto.info("Done sending asset and vulnerability data to XSIAM.") elif command == "tenable-sc-launch-scan": return_results(launch_scan_command(args, client)) else: return_results(command_dict[command](client, args)) except Exception as e: return_error(f"Failed to execute {command} command. Error: {e!s}") if __name__ in ("__main__", "__builtin__", "builtins"): main()