Kennav2
Use the Kenna v2 integration to search and update vulnerabilities, schedule a run connector, and manage tags and attributes.
Vulnerability Management · Kenna
Details
| ID | Kennav2 |
|---|---|
| Provider | Cisco Systems |
| Category | Vulnerability Management |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM EDR Cortex Cloud Cloud Runtime Security |
README
Use the Kenna v2 integration to search and update vulnerabilities, schedule a run connector, and manage tags and attributes.
This integration was integrated and tested with version 1.0 of Kenna.
Configure Kenna v2 on Cortex XSOAR
- Navigate to Settings > Integrations > Instances.
- Search for Kenna v2.
-
Click Add instance to create and configure a new integration instance.
Parameter Required Server URL (e.g. https://api.kennasecurity.com) False Kenna API key False Use system proxy settings False Trust any certificate (not secure) False - Click Test to validate the URLs, token, and connection.
Commands
You can execute these commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
kenna-search-vulnerabilities
Searches for vulnerabilities in Kenna.
Base Command
kenna-search-vulnerabilities
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The vulnerability ID for which to search. | Optional |
| top-priority | Whether to return vulnerabilities that Kenna deems a top priority to fix. Possible values are: true, false. | Optional |
| min-score | The minimum vulnerability score for which to return vulnerabilities. | Optional |
| status | The status of the vulnerability. Possible values are: open, closed, risk_accepted, false_positive. | Optional |
| limit | The maximum number of vulnerabilities to return. Default is 500. | Optional |
| to_context | Whether to flush to context. Possible values are: True, False. Default is True. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Kenna.Vulnerabilities.AssetID | Number | The asset ID related to the vulnerability. |
| Kenna.Vulnerabilities.Connectors.DefinitionName | String | The connector definition name related to the vulnerability. |
| Kenna.Vulnerabilities.Connectors.ID | Number | The connector ID related to the vulnerability. |
| Kenna.Vulnerabilities.Connectors.Name | String | The connector name related to the vulnerability. |
| Kenna.Vulnerabilities.Connectors.Vendor | String | The connector vendor related to the vulnerability. |
| Kenna.Vulnerabilities.CveID | String | The CVE ID related to the vulnerability. |
| Kenna.Vulnerabilities.FixID | String | The fix ID related to the vulnerability. |
| Kenna.Vulnerabilities.Patch | Boolean | Whether there is a patch related to the vulnerability. |
| Kenna.Vulnerabilities.ScannerVulnerabilities.ExternalID | String | The vulnerability scanner external ID. |
| Kenna.Vulnerabilities.ScannerVulnerabilities.Open | Boolean | Whether the vulnerability scanner is open. |
| Kenna.Vulnerabilities.ScannerVulnerabilities.Port | Number | The vulnerability scanner port. |
| Kenna.Vulnerabilities.Score | Number | The vulnerability score. |
| Kenna.Vulnerabilities.ServiceTicket.DueDate | Date | The service ticket due date. |
| Kenna.Vulnerabilities.ServiceTicket.ExternalIdentifier | String | The service ticket external identifier. |
| Kenna.Vulnerabilities.ServiceTicket.Status | String | The service ticket status. |
| Kenna.Vulnerabilities.ServiceTicket.TicketType | String | The service ticket type. |
| Kenna.Vulnerabilities.Severity | Number | The vulnerability severity. |
| Kenna.Vulnerabilities.Status | String | The vulnerability status. |
| Kenna.Vulnerabilities.Threat | Number | The vulnerability threat. |
| Kenna.Vulnerabilities.TopPriority | Number | The vulnerability priority. |
| Kenna.Vulnerabilities.ID | Number | The vulnerability ID. |
Command example
!kenna-search-vulnerabilities id=dummy limit=1 to_context=True
Context Example
{
"Kenna": {
"Vulnerabilities": {
"AssetID": "dummy",
"Connectors": [
{
"DefinitionName": "Dummy XML",
"ID": 0,
"Name": "Dummy XML",
"Vendor": "Dummy"
},
{
"DefinitionName": "Kenna Data",
"ID": 1,
"Name": "Generic",
"Vendor": "Dummy"
}
],
"CveID": "CVE-2015-0000",
"FixID": 00000,
"ID": 00000,
"Patch": true,
"ScannerVulnerabilities": [
{
"ExternalID": "generic scanner-id CVE-2015-0000",
"Open": true,
"Port": null
},
{
"ExternalID": "dummy-external-id CVE-2015-0000 0000-0000-0000-0000-0000",
"Open": true,
"Port": null
}
],
"Score": 100,
"Severity": 10,
"Status": "open",
"Threat": 10,
"TopPriority": true
}
}
}
Human Readable Output
Kenna Vulnerabilities
Name Score id CVE-2015-0000 100 00000
kenna-run-connector
Executes a run of the specified connector. If file based, it will use the most recently uploaded data file.
Base Command
kenna-run-connector
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The connector ID to run. | Required |
Context Output
There is no context output for this command.
Command example
!kenna-run-connector id=dummy
Human Readable Output
Connector dummy ran successfully.
kenna-search-fixes
Filters fixes by a given set of vulnerability and asset parameters and returns the filtered fixes.
Base Command
kenna-search-fixes
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The vulnerability ID for which to search. | Optional |
| top-priority | Whether to return vulnerabilities that Kenna deems a top priority to fix. Possible values are: true, false. | Optional |
| min-score | The minimum vulnerability score for which to return vulnerabilities. | Optional |
| status | The status of the vulnerability. Possible values are: open, closed, risk_accepted, false_positive. | Optional |
| limit | The maximum number of vulnerabilities to return. Default is 500. | Optional |
| to_context | Whether to flush to context. Possible values are: True, False. Default is True. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Kenna.Fixes.ID | Number | The fix ID. |
| Kenna.Fixes.Title | String | The fix title. |
| Kenna.Fixes.Assets.ID | Number | The asset ID related to the current fix. |
| Kenna.Fixes.Assets.Locator | String | The asset locator related to the current fix. |
| Kenna.Fixes.Assets.PrimaryLocator | String | The asset primary locator related to the current fix. |
| Kenna.Fixes.Assets.DisplayLocator | String | The asset display locator related to the current fix. |
| Kenna.Fixes.Vulnerabilities.ID | Number | The vulnerability ID related to the current fix. |
| Kenna.Fixes.Vulnerabilities.ServiceTicketStatus | String | The vulnerability service ticket status related to the current fix. |
| Kenna.Fixes.Vulnerabilities.ScannerIDs | Number | The vulnerability scanner IDs related to the current fix. |
| Kenna.Fixes.CveID | String | The CVE-ID list related to the current fix. |
| Kenna.Fixes.LastUpdatedAt | String | The timestamp when the current fix was last updated. |
| Kenna.Fixes.Category | String | The category of fix. |
| Kenna.Fixes.VulnerabilityCount | Number | The vulnerability count of the fix. |
| Kenna.Fixes.MaxScore | Number | The maximum score of the fix. |
Command example
!kenna-search-fixes limit=2 id=dummy to_context=True
Context Example
{
"Kenna": {
"Fixes": {
"Assets": [
{
"DisplayLocator": "0.0.0",
"ID": 0,
"Locator": "0.0.0",
"PrimaryLocator": "ip_address"
}
],
"Category": null,
"CveID": [
"CVE-2015-0000"
],
"ID": 0,
"LastUpdatedAt": "2019-10-24T19:13:29.000Z",
"MaxScore": 100,
"Title": "CVE-2015-0000",
"VulnerabilityCount": 1
}
}
}
Human Readable Output
CVE-2015-0000
ID: 0
1 vulnerabilities affected
Diagnosis
Related CVE IDs: CVE-2015-0000
kenna-update-asset
Updates the attributes of a single asset.
Base Command
kenna-update-asset
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The ID of the asset to update. | Required |
| notes | Notes about the asset. | Required |
| inactive | Whether to deactivate the asset. Possible values are: true, false. | Optional |
Context Output
There is no context output for this command.
Command example
!kenna-update-asset id=dummy notes="New asset info"
Human Readable Output
Asset with ID dummy was successfully updated.
kenna-update-vulnerability
Updates the attributes of a single vulnerability.
Base Command
kenna-update-vulnerability
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The ID of the vulnerability to update. | Required |
| status | The status of the vulnerability. Possible values are: open, closed, risk_accepted, false_positive. | Optional |
| notes | Notes about the vulnerability. | Optional |
Context Output
There is no context output for this command.
Command example
!kenna-update-vulnerability id=dummy status=open notes="Test"
Human Readable Output
Asset dummy was updated
kenna-get-connectors
Returns all connectors.
Base Command
kenna-get-connectors
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| Kenna.ConnectorsList.ID | Number | The connector ID. |
| Kenna.ConnectorsList.Name | String | The connector name. |
| Kenna.ConnectorsList.Running | Boolean | The running connector. |
| Kenna.ConnectorsList.Host | String | The connector host. |
Command example
#### Context Example
```json
{
"Kenna": {
"ConnectorsList": [
{
"Host": null,
"ID": 0,
"Name": "XML",
"Running": false
},
{
"Host": null,
"ID": 1,
"Name": "Generic",
"Running": false
}
]
}
}
Human Readable Output
Kenna Connectors
Host ID Name Running 0 XML false 1 Generic false
kenna-search-assets
Searches for assets.
Base Command
kenna-search-assets
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The asset ID to search for. | Optional |
| hostname | The hostname of the asset to search for. | Optional |
| min-score | The minimum vulnerability score for which to return vulnerabilities. | Optional |
| tags | The tags by which to search. | Optional |
| limit | The maximum number of vulnerabilities to return. Default is 500. | Optional |
| to_context | Whether to print output to context. Possible values are: True, False. Default is True. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Kenna.Assets.ID | Number | The asset ID. |
| Kenna.Assets.ExternalID | String | The asset external ID. |
| Kenna.Assets.Hostname | String | The hostname of the asset. |
| Kenna.Assets.IpAddress | String | The asset IP address. |
| Kenna.Assets.Score | Number | The asset risk score. |
| Kenna.Assets.VulnerabilitiesCount | Number | The number of vulnerabilities associated with the asset. |
| Kenna.Assets.OperatingSystem | String | The asset operating system. |
| Kenna.Assets.Tags | String | A list of the asset’s tags. |
| Kenna.Assets.Fqdn | String | The asset FQDN. |
| Kenna.Assets.Status | String | The asset status. |
| Kenna.Assets.Owner | String | The asset owner. |
| Kenna.Assets.Priority | Number | The asset priority. |
| Kenna.Assets.Notes | String | Notes of current asset. |
| Kenna.Assets.OperatingSystem | String | Operating system of the asset. |
Command example
!kenna-search-assets limit=2 to_context=True
Context Example
{
"Kenna": {
"Assets": [
{
"Fqdn": null,
"Hostname": null,
"ID": 0,
"IpAddress": "0.0.0",
"Notes": "New asset info",
"OperatingSystem": "Windows",
"Owner": null,
"Priority": 10,
"Score": 1000,
"Status": "active",
"Tags": [
"Dummy"
],
"VulnerabilitiesCount": 10
},
{
"Fqdn": null,
"Hostname": null,
"ID": 1,
"IpAddress": "0.0.0",
"Notes": null,
"OperatingSystem": "Windows",
"Owner": null,
"Priority": 10,
"Score": 1000,
"Status": "active",
"Tags": [
"Category"
],
"VulnerabilitiesCount": 10
}
]
}
}
Human Readable Output
Kenna Assets
IP-address Operating System Score id 0.0.0 Windows 1000 0 0.0.0 Windows 1000 1
kenna-get-asset-vulnerabilities
Gets vulnerabilities of the specified asset.
Base Command
kenna-get-asset-vulnerabilities
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The asset ID for which to get vulnerabilities. | Required |
| limit | The maximum number of vulnerabilities to return. Default is 500. | Optional |
| to_context | Whether to print output to context. Possible values are: True, False. Default is True. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Kenna.VulnerabilitiesOfAsset.AssetID | Number | The ID of the asset that this vulnerability is associated with. |
| Kenna.VulnerabilitiesOfAsset.CveID | String | The CVE ID of the vulnerability associated with the asset. |
| Kenna.VulnerabilitiesOfAsset.ID | Number | The ID of the vulnerability associated withe the asset. |
| Kenna.VulnerabilitiesOfAsset.Patch | Boolean | Whether there is a patch for the vulnerability associated with the asset. |
| Kenna.VulnerabilitiesOfAsset.Status | String | The status of the vulnerability associated with the asset. |
| Kenna.VulnerabilitiesOfAsset.TopPriority | Boolean | Whether the vulnerability associated with the asset is a top priority. |
| Kenna.VulnerabilitiesOfAsset.Score | Number | The score of the vulnerability associated with the asset. |
Command example
!kenna-get-asset-vulnerabilities id=dummy limit=2 to_context=True
Context Example
{
"Kenna": {
"VulnerabilitiesOfAsset": [
{
"AssetID": "dummy",
"CveID": "CVE-2015-0000",
"ID": 0,
"Patch": true,
"Score": 100,
"Status": "open",
"TopPriority": true
},
{
"AssetID": "dummy",
"CveID": "CVE-2015-0001",
"ID": 1,
"Patch": true,
"Score": 100,
"Status": "open",
"TopPriority": true
}
]
}
}
Human Readable Output
Kenna Vulnerabilities
Name Score id CVE-2015-0000 100 0 CVE-2015-0001 100 1
kenna-add-tag
Adds a tag to the specified asset.
Base Command
kenna-add-tag
Input
| Argument Name | Description | Required |
|---|---|---|
| tag | A comma-separated list of tags to add to the asset. | Required |
| id | The asset ID to which to add the tag. | Required |
Context Output
There is no context output for this command.
Command example
!kenna-add-tag tag="Test tag" id=dummy
Human Readable Output
Tag Test tag was added to asset dummy
kenna-delete-tag
Deletes tags from the specified asset.
Base Command
kenna-delete-tag
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The asset ID from which to delete the tag. | Required |
| tag | The tag to delete. | Required |
Context Output
There is no context output for this command.
Command example
!kenna-delete-tag id=dummy tag="Test tag"
Human Readable Output
Tag Test tag was successfully removed from asset.
kenna-get-connector-runs
Returns JSON data on all the runs of a given connector.
Base Command
kenna-get-connector-runs
Input
| Argument Name | Description | Required |
|---|---|---|
| connector_id | Unique numerical ID of the connector. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| Kenna.ConnectorRunsList.ID | Number | Connector Run ID. |
| Kenna.ConnectorRunsList.StartTime | Number | Connector Run Start Time. |
| Kenna.ConnectorRunsList.EndTime | string | Connector Run End Time. |
| Kenna.ConnectorRunsList.Success | boolean | Boolean value showing connector success. |
| Kenna.ConnectorRunsList.TotalPayload | Number | Total connector payloads. |
| Kenna.ConnectorRunsList.ProcessedPayload | Number | Total payloads processed the connector. |
| Kenna.ConnectorRunsList.FailedPayload | Number | Total failed payloads. |
| Kenna.ConnectorRunsList.ProcessedAssets | Number | Amount of processed assets. |
| Kenna.ConnectorRunsList.AssetsWithTagsReset | Number | Amount of assets with reset tags. |
| Kenna.ConnectorRunsList.ProcessedScannerVulnerabilities | Number | Amount of processed scanners with vulnerabilities. |
| Kenna.ConnectorRunsList.UpdatedScannerVulnerabilities | Number | Amount of updated scanners with vulnerabilities. |
| Kenna.ConnectorRunsList.CreatedScannerVulnerabilities | Number | Amount of created scanners with vulnerabilities. |
| Kenna.ConnectorRunsList.ClosedScannerVulnerabilities | Number | Amount of closed scanners with vulnerabilities. |
| Kenna.ConnectorRunsList.AutoclosedScannerVulnerabilities | Number | Amount of auto-closed scanners with vulnerabilities. |
| Kenna.ConnectorRunsList.ReopenedScannerVulnerabilities | number | Amount of reopened scanners with vulnerabilities. |
| Kenna.ConnectorRunsList.ClosedVulnerabilities | Number | Amount of closed vulnerabilities. |
| Kenna.ConnectorRunsList.AutoclosedVulnerabilities | Number | Amount of auto-closed vulnerabilities. |
| Kenna.ConnectorRunsList.ReopenedVulnerabilities | Number | Amount of re-opened vulnerabilities. |
Command example
!kenna-get-connector-runs connector_id=dummy
Context Example
{
"Kenna": {
"ConnectorRunsList": {
"AssetsWithTagsReset": 0,
"AutoclosedScannerVulnerabilities": 0,
"AutoclosedVulnerabilities": 0,
"ClosedScannerVulnerabilities": 0,
"ClosedVulnerabilities": 0,
"CreatedScannerVulnerabilities": 0,
"EndTime": "2019-10-24T19:13:36.000Z",
"FailedPayload": 0,
"ID": 0,
"ProcessedAssets": 0,
"ProcessedPayload": null,
"ProcessedScannerVulnerabilities": 0,
"ReopenedScannerVulnerabilities": 0,
"ReopenedVulnerabilities": 0,
"StartTime": "2019-10-24T19:02:02.000Z",
"Success": true,
"TotalPayload": 0,
"UpdatedScannerVulnerabilities": 0
}
}
}
Human Readable Output
Kenna Connector Runs
AssetsWithTagsReset AutoclosedScannerVulnerabilities AutoclosedVulnerabilities ClosedScannerVulnerabilities ClosedVulnerabilities CreatedScannerVulnerabilities EndTime FailedPayload ID ProcessedAssets ProcessedPayload ProcessedScannerVulnerabilities ReopenedScannerVulnerabilities ReopenedVulnerabilities StartTime Success TotalPayload UpdatedScannerVulnerabilities 0 0 0 0 0 0 2019-10-24T19:13:36.000Z 0 0 0 0 0 0 0 2019-10-24T19:02:02.000Z true 0 0
kenna-search-assets-by-external-id
Search assets by external ID.
Base Command
kenna-search-assets-by-external-id
Input
| Argument Name | Description | Required |
|---|---|---|
| external_id | The external ID of the asset. | Required |
| to_context | Whether to put data in context. Possible values are: true, false. | Optional |
| limit | The maximum number of assets to return. Default is 500. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Kenna.Assets.ID | Number | The asset ID. |
| Kenna.Assets.Notes | String | Notes of current asset. |
| Kenna.Assets.VulnerabilitiesCount | Number | Count of vulnerabilities of current asset. |
| Kenna.Assets.Hostname | String | Hostname of current asset. |
| Kenna.Assets.Score | Number | Score of current asset. |
| Kenna.Assets.IpAddress | String | IP of current asset. |
| Kenna.Assets.OperatingSystem | String | Operating system of current asset. |
Command example
!kenna-search-assets-by-external-id external_id=dummy limit=2 to_context=true
Context Example
{
"Kenna": {
"Assets": [
{
"Fqdn": null,
"Hostname": null,
"ID": 0,
"IpAddress": "0.0.0",
"Notes": "New asset info",
"OperatingSystem": "Windows",
"Owner": null,
"Priority": 10,
"Score": 1000,
"Status": "active",
"Tags": [
"Category10",
"Category9"
],
"VulnerabilitiesCount": 10
},
{
"Fqdn": null,
"Hostname": null,
"ID": 1,
"IpAddress": "0.0.0",
"Notes": null,
"OperatingSystem": "Windows",
"Owner": null,
"Priority": 10,
"Score": 1000,
"Status": "active",
"Tags": [
"Category5",
"Category7"
],
"VulnerabilitiesCount": 10
}
]
}
}
Human Readable Output
Kenna Assets
IP-address Operating System Score id 0.0.0 Windows 1000 0 0.0.0 Windows 1000 0
Configuration parameters
url— Server URL (e.g. https://api.kennasecurity.com)key— Kenna API keycredentials_key—proxy— Use system proxy settingsinsecure— Trust any certificate (not secure)
Commands (12)
-
kenna-add-tagAdds a tag to the specified asset.
-
kenna-delete-tagDeletes tags from the specified asset.
-
kenna-get-asset-vulnerabilitiesGets vulnerabilities of the specified asset.
-
kenna-get-connector-runsReturns JSON data on all the runs of a given connector.
-
kenna-get-connectorsReturns all connectors.
-
kenna-run-connectorExecutes a run of the specified connector. If file based, it will use the most recently uploaded data file.
-
kenna-search-assetsSearches for assets.
-
kenna-search-assets-by-external-idSearch assets by external ID.
-
kenna-search-fixesFilters fixes by a given set of vulnerability and asset parameters and returns the filtered fixes.
-
kenna-search-vulnerabilitiesSearches for vulnerabilities in Kenna.
-
kenna-update-assetUpdates the attributes of a single asset.
-
kenna-update-vulnerabilityUpdates the attributes of a single vulnerability.
from collections.abc import Callable import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 def parse_response(raw_data: List[Dict[str, Any]], wanted_keys: List[Any], actual_keys: List[Any]) -> List[Dict[str, Any]]: """Lists all raw data and return outputs in Demisto's format. Args: raw_data: raw response from the api. wanted_keys: The keys as we would like them to be. actual_keys :The keys as they are in raw response. Returns: Specific Keys from the raw data. """ context_list = [] for raw in raw_data: context = {} for wanted_key, actual_key in zip(wanted_keys, actual_keys): if isinstance(wanted_key, list): inner_raw = raw.get(actual_key[0]) if inner_raw: lst_inner = [] for in_raw in inner_raw: inner_dict = {} for inner_wanted_key, inner_actual_key in zip(wanted_key[1:], actual_key[1:]): inner_dict.update({inner_wanted_key: in_raw.get(inner_actual_key)}) lst_inner.append(inner_dict) context.update({wanted_key[0]: lst_inner}) else: context.update({wanted_key: raw.get(actual_key)}) context_list.append(context) return context_list class Client(BaseClient): def __init__(self, base_url: str, api_key: str, verify: bool, proxy: bool): header = {"X-Risk-Token": api_key, "Content-Type": "application/json", "Accept": "application/json"} super().__init__(base_url=base_url, verify=verify, proxy=proxy, headers=header) def http_request(self, message: str, suffix: str, params: Optional[dict] = None, data: Optional[dict] = None): """Connects to api and Returns response. Args: message: The HTTP message, for example: GET, POST, and so on suffix :The API endpoint. params: URL parameters to specify the query. data:The data to send in a specific request. Returns: response from the api. """ url = f"{self._base_url}{suffix}" try: response = requests.request( message, url, headers=self._headers, params=params, json=data, verify=self._verify, ) except requests.exceptions.SSLError as err: raise DemistoException(f"Connection error in the API call to Kenna.\nCheck your not secure parameter.\n\n{err}") except requests.ConnectionError as err: raise DemistoException(f"Connection error in the API call to Kenna.\nCheck your Server URL parameter.\n\n{err}") try: response_dict = response.json() if response.text else {} if not response.ok: if response_dict.get("error") == "unauthorized": raise DemistoException( f'Connection error in the API call to Kenna.\n' f'Check your Api Key parameter.\n\n{response_dict.get("message")}' ) else: raise DemistoException( f'API call to Kenna failed with error code: {response.status_code}.\n' f'Error: {response_dict.get("error")}\n' f'Message: {response_dict.get("message")}' ) elif response.status_code == 204: return {"status": "success"} return response_dict except TypeError: raise Exception(f"Error in API call to Kenna, could not parse result [{response.status_code}]") def test_module(client: Client) -> str: """ Tests the connection to the Kenna v2 API by performing a basic GET request. """ client.http_request("GET", "/assets") return "ok" def search_vulnerabilities(client: Client, args: dict) -> tuple[str, Dict[str, Any], List[Dict[str, Any]]]: """Search vulnerability command. Args: client: Client which connects to api args: arguments for the request Returns: Human Readable Entry Context Raw Data """ url_suffix = "/vulnerabilities/search" limit: int = int(args.get("limit", 500)) to_context = args.get("to_context") human_readable = [] context: Dict[str, Any] = {} params = { "id[]": argToList(args.get("id")), "top_priority[]": argToList(args.get("top-priority")), "min_risk_meter_score": args.get("min-score"), "status[]": argToList(args.get("status")), } response = client.http_request(message="GET", suffix=url_suffix, params=params).get("vulnerabilities") if response: vulnerability_list = response[:limit] wanted_keys = [ "AssetID", ["Connectors", "DefinitionName", "ID", "Name", "Vendor"], "CveID", "FixID", "ID", "Patch", "Score", ["ScannerVulnerabilities", "ExternalID", "Open", "Port"], "Severity", "Status", "Threat", "TopPriority", ["ServiceTicket", "DueDate", "ExternalIdentifier", "Status", "TicketType"], ] actual_keys = [ "asset_id", ["connectors", "connector_definition_name", "id", "name", "vendor"], "cve_id", "fix_id", "id", "patch", "risk_meter_score", ["scanner_vulnerabilities", "external_unique_id", "open", "port"], "severity", "status", "threat", "top_priority", ["service_ticket", "due_date", "external_identifier", "status", "ticket_type"], ] context_list = parse_response(vulnerability_list, wanted_keys, actual_keys) for lst in vulnerability_list: human_readable.append({"id": lst.get("id"), "Name": lst.get("cve_id"), "Score": lst.get("risk_meter_score")}) context = {"Kenna.Vulnerabilities(val.ID === obj.ID)": context_list} human_readable_markdown = tableToMarkdown("Kenna Vulnerabilities", human_readable, removeNull=True) else: human_readable_markdown = "no vulnerabilities found." if to_context == "False": return human_readable_markdown, {}, response return human_readable_markdown, context, response def get_connectors(client: Client, *_) -> tuple[str, Dict[str, Any], List[Dict[str, Any]]]: """Get Connectors command. Args: client: Client which connects to api Returns: Human Readable Entry Context Raw Data """ url_suffix = "/connectors" human_readable = [] context: Dict[str, Any] = {} connectors = client.http_request(message="GET", suffix=url_suffix).get("connectors") if connectors: wanted_keys = ["Host", "Name", "Running", "ID"] actual_keys = ["host", "name", "running", "id"] context_list = parse_response(connectors, wanted_keys, actual_keys) for connector in connectors: curr_dict = { "Host": connector.get("host"), "Name": connector.get("name"), "Running": connector.get("running"), "ID": connector.get("id"), } human_readable.append(curr_dict) context = {"Kenna.ConnectorsList(val.ID === obj.ID)": context_list} human_readable_markdown = tableToMarkdown("Kenna Connectors", human_readable, removeNull=True) else: human_readable_markdown = "no connectors in get response." return human_readable_markdown, context, connectors def inactivate_asset(client: Client, args: dict[str, str]) -> CommandResults: """ Inactivate an asset. This function sends a PUT request to the '/assets/{asset_id}' endpoint with the 'inactive' field set to True. Args: client (Client): The client to use for the HTTP request. args (dict): A dictionary of arguments. Expected keys are 'asset_id' and optionally 'notes'. Returns: CommandResults: A CommandResults object. """ asset_id = args["asset_id"] url_suffix = f"/assets/{asset_id}" asset = {"asset": {"inactive": argToBoolean(args["inactive"]), "notes": args["notes"]}} result = client.http_request(message="PUT", suffix=url_suffix, data=asset) if result.get("status") != "success": return CommandResults(readable_output=f"Could not inactivate asset with ID {asset_id}.", raw_response=result) return CommandResults(readable_output=f"Asset with ID {asset_id} was successfully inactivated.") def get_connector_runs(client: Client, args: dict) -> tuple[str, Dict[str, Any], List[Dict[str, Any]]]: """Get Connector Runs command. Args: client: Client which connects to api Returns: Human Readable Entry Context Raw Data """ connector_id = str(args.get("connector_id")) url_suffix = f"/connectors/{connector_id}/connector_runs" human_readable = [] context: Dict[str, Any] = {} connectors: List[Dict[str, Any]] = client.http_request(message="GET", suffix=url_suffix) if connectors: actual_keys = [ "id", "start_time", "end_time", "success", "total_payload_count", "processed_palyoad_count", "failed_payload_count", "processed_assets_count", "assets_with_tags_reset_count", "processed_scanner_vuln_count", "updated_scanner_vuln_count", "created_scanner_vuln_count", "closed_scanner_vuln_count", "autoclosed_scanner_vuln_count", "reopened_scanner_vuln_count", "closed_vuln_count", "autoclosed_vuln_count", "reopened_vuln_count", ] wanted_keys = [ "ID", "StartTime", "EndTime", "Success", "TotalPayload", "ProcessedPayload", "FailedPayload", "ProcessedAssets", "AssetsWithTagsReset", "ProcessedScannerVulnerabilities", "UpdatedScannerVulnerabilities", "CreatedScannerVulnerabilities", "ClosedScannerVulnerabilities", "AutoclosedScannerVulnerabilities", "ReopenedScannerVulnerabilities", "ClosedVulnerabilities", "AutoclosedVulnerabilities", "ReopenedVulnerabilities", ] context_list = parse_response(connectors, wanted_keys, actual_keys) for connector in connectors: curr_dict = { "ID": connector.get("id"), "StartTime": connector.get("start_time"), "EndTime": connector.get("end_time"), "Success": connector.get("success"), "TotalPayload": connector.get("total_payload_count"), "ProcessedPayload": connector.get("total_payload_count"), "FailedPayload": connector.get("failed_payload_count"), "ProcessedAssets": connector.get("processed_assets_count"), "AssetsWithTagsReset": connector.get("assets_with_tags_reset_count"), "ProcessedScannerVulnerabilities": connector.get("processed_scanner_vuln_count"), "UpdatedScannerVulnerabilities": connector.get("updated_scanner_vuln_count"), "CreatedScannerVulnerabilities": connector.get("created_scanner_vuln_count"), "ClosedScannerVulnerabilities": connector.get("closed_scanner_vuln_count"), "AutoclosedScannerVulnerabilities": connector.get("autoclosed_scanner_vuln_count"), "ReopenedScannerVulnerabilities": connector.get("reopened_scanner_vuln_count"), "ClosedVulnerabilities": connector.get("closed_vuln_count"), "AutoclosedVulnerabilities": connector.get("closed_vuln_count"), "ReopenedVulnerabilities": connector.get("reopened_vuln_count"), } human_readable.append(curr_dict) context = {"Kenna.ConnectorRunsList(val.ID === obj.ID)": context_list} human_readable_markdown = tableToMarkdown("Kenna Connector Runs", human_readable, removeNull=True) else: human_readable_markdown = "no connectors in get response." return human_readable_markdown, context, connectors def run_connector(client: Client, args: dict) -> tuple[str, Dict[str, Any], List[Dict[str, Any]]]: """Run Connector command. Args: client: Client which connects to api args: arguments for the request Returns: Success/ Failure , according to the response """ args_id: str = str(args.get("id")) url_suffix = f"/connectors/{args_id}/run" run_response = client.http_request(message="GET", suffix=url_suffix) if run_response and run_response.get("success") == "true": return f"Connector {args_id} ran successfully.", {}, [] return f"Connector {args_id} did not ran successfully.", {}, [] def search_fixes(client: Client, args: dict) -> tuple[str, Dict[str, Any], List[Dict[str, Any]]]: """Search Fixes command. Args: client: Client which connects to api args: arguments for the request Returns: Human Readable Entry Context Raw Data """ human_readable_markdown = "" url_suffix = "/fixes/search" limit: int = int(args.get("limit", 500)) to_context = args.get("to_context") context: Dict[str, Any] = {} params = { "id[]": argToList(args.get("id")), "top_priority[]": argToList(args.get("top-priority")), "min_risk_meter_score": args.get("min-score"), "status[]": argToList(args.get("status")), "per_page": limit, } response = client.http_request(message="GET", suffix=url_suffix, params=params).get("fixes") if response: wanted_keys = [ "ID", "Title", ["Assets", "ID", "Locator", "PrimaryLocator", "DisplayLocator"], ["Vulnerabilities", "ID", "ServiceTicketStatus", "ScannerIDs"], "CveID", "LastUpdatedAt", "Category", "VulnerabilityCount", "MaxScore", ] actual_keys = [ "id", "title", ["assets", "id", "locator", "primary_locator", "display_locator"], ["vulnerabilities", "id", "service_ticket_status", "scanner_ids"], "cves", "updated_at", "category", "vuln_count", "max_vuln_score", ] context_list = parse_response(response, wanted_keys, actual_keys) remove_html = re.compile(r"<[^>]+>") for fix in response: if fix: human_readable_markdown += str(fix.get("title")) + "\n" human_readable_markdown += "#### ID: " + str(fix.get("id")) + "\n" human_readable_markdown += str(fix.get("vuln_count")) + " vulnerabilities affected\n" human_readable_markdown += "#### Diagnosis:\n" human_readable_markdown += remove_html.sub(" ", str(fix.get("diagnosis"))) + "\n" + " " + "\n" context = {"Kenna.Fixes(val.ID === obj.ID)": context_list} else: human_readable_markdown = "no fixes in response." if to_context == "False": return human_readable_markdown, {}, response return human_readable_markdown, context, response def update_asset_command(client: Client, args: dict[str, str]) -> CommandResults: """ Update an asset in the Kenna Security Platform. Args: client (Client): The Kenna client object. args (dict[str, str]): A dictionary containing: - asset ID (required) - notes (required) - inactive (optional) Returns: CommandResults: If the update is successful,the result will contain a success message. If the update fails, the result will contain an error message. """ asset_id = args["id"] url_suffix = f"/assets/{asset_id}" asset = {"asset": {"notes": args["notes"]}} if inactive := args.get("inactive"): asset["asset"].update({"inactive": argToBoolean(inactive)}) result = client.http_request(message="PUT", suffix=url_suffix, data=asset) if result.get("status") != "success": return CommandResults(readable_output=f"Could not update asset with ID {asset_id}.", raw_response=result) return CommandResults(readable_output=f"Asset with ID {asset_id} was successfully updated.") def update_vulnerability(client: Client, args: dict) -> tuple[str, Dict[str, Any], List[Dict[str, Any]]]: """Update Vulnerabilities command. Args: client: Client which connects to api args: arguments for the request Returns: Success/ Failure , according to the response """ params_to_update: dict = {"vulnerability": {}} args_id = str(args.get("id")) status = str(args.get("status")) notes = str(args.get("notes")) if notes: params_to_update["vulnerability"].update({"notes": notes}) if status: params_to_update["vulnerability"].update({"status": status}) url_suffix = f"/vulnerabilities/{args_id}" result = client.http_request(message="PUT", suffix=url_suffix, data=params_to_update) if result.get("status") != "success": return "Could not update asset.", {}, [] return f"Asset {args_id} was updated", {}, [] def search_assets_command(client: Client, args: dict[str, Any]) -> CommandResults: """ Search for assets in Kenna based on the provided parameters. Args: client (Client): The Kenna client object. args (dict[str, Any]): A dictionary of arguments provided by the user. The optional arguments are: - 'limit': The maximum number of assets to return. Default is 500. - 'to_context': Whether to include the results in the context. Default is True. - 'hostname': The hostname to search for. Default is an empty string. - 'tags': A list of tags to search for. Default is an empty list. - 'id': A list of asset IDs to search for. Default is an empty list. - 'min-score': The minimum vulnerability score for which to return vulnerabilities. Default is None. Returns: CommandResults: A CommandResults object containing the results of the search. """ url_suffix = "/assets/search" limit = arg_to_number(args.get("limit")) or 500 to_context = argToBoolean(args.get("to_context", True)) hostname: str = args.get("hostname", "") tags = argToList(args.get("tags")) hostname_query = f'hostname:({hostname.replace(",", " ")})' if hostname else hostname params = { "id[]": argToList(args.get("id")), "q": hostname_query, "min_risk_meter_score": args.get("min-score"), "tags[]": tags, } response = client.http_request(message="GET", suffix=url_suffix, params=params).get("assets") if not response: return CommandResults(readable_output="No assets were found.", raw_response=response) assets = response[:limit] if len(assets) > limit: demisto.debug(f"found {len(assets)} assets, using the limit arg to keep only the first {limit} ones") wanted_keys = [ "ID", "Hostname", "Score", "IpAddress", "VulnerabilitiesCount", "OperatingSystem", "Tags", "Fqdn", "Status", "Owner", "Priority", "Notes", "OperatingSystem", "ExternalID", ] actual_keys = [ "id", "hostname", "risk_meter_score", "ip_address", "vulnerabilities_count", "operating_system", "tags", "fqdn", "status", "owner", "priority", "notes", "operating_system", "external_id", ] context: list[dict[str, Any]] = parse_response(assets, wanted_keys, actual_keys) human_readable = [] for lst in assets: human_readable.append( { "id": lst.get("id"), "Hostname": lst.get("hostname"), "IP-address": lst.get("ip_address"), "Vulnerabilities Count": args.get("vulnerabilities_count"), "Operating System": lst.get("operating_system"), "Score": lst.get("risk_meter_score"), } ) return CommandResults( outputs_prefix="Kenna.Assets", outputs_key_field="ID", readable_output=tableToMarkdown("Kenna Assets", human_readable, removeNull=True), outputs=context if to_context else None, raw_response=response, ) def get_asset_vulnerabilities(client: Client, args: dict) -> tuple[str, Dict[str, Any], List[Dict[str, Any]]]: """Get Asset by Vulnerability command. Args: client: Client which connects to api args: arguments for the request Returns: Human Readable Entry Context Raw Data """ args_id = str(args.get("id")) limit: int = int(args.get("limit", 500)) to_context = args.get("to_context") url_suffix = f"/assets/{args_id}/vulnerabilities" human_readable = [] context: Dict[str, Any] = {} response = client.http_request(message="GET", suffix=url_suffix).get("vulnerabilities") if response: vulnerabilities_list = response[:limit] wanted_keys: List[Any] = ["AssetID", "CveID", "ID", "Patch", "Status", "TopPriority", "Score"] actual_keys: List[Any] = ["asset_id", "cve_id", "id", "patch", "status", "top_priority", "risk_meter_score"] context_list: List[Dict[str, Any]] = parse_response(vulnerabilities_list, wanted_keys, actual_keys) for lst in vulnerabilities_list: human_readable.append({"id": lst.get("id"), "Name": lst.get("cve_id"), "Score": lst.get("risk_meter_score")}) context = {"Kenna.VulnerabilitiesOfAsset(val.ID === obj.ID)": context_list} human_readable_markdown = tableToMarkdown("Kenna Vulnerabilities", human_readable, removeNull=True) else: human_readable_markdown = "no vulnerabilities in response" if to_context == "False": return human_readable_markdown, {}, response return human_readable_markdown, context, response def add_tags(client: Client, args: dict) -> tuple[str, Dict[str, Any], List[Dict[str, Any]]]: """Add tags command. Args: client: Client which connects to api args: arguments for the request Returns: Success/ Failure , according to the response """ asset_id = args.get("id") tags = args.get("tag") url_suffix = f"/assets/{asset_id}/tags" asset = {"asset": {"tags": tags}} result = client.http_request(message="PUT", suffix=url_suffix, data=asset) if result.get("status") != "success": return f"Tag {tags} was not added to asset {asset_id}.", {}, [] return f"Tag {tags} was added to asset {asset_id}.", {}, [] def delete_tags(client: Client, args: dict) -> tuple[str, dict[str, Any], list[dict[str, Any]]]: """Delete tags command. Args: client: Client which connects to api args: arguments for the request Returns: Success/ Failure , according to the response """ asset_id = args.get("id") tags = args.get("tag") url_suffix = f"/assets/{asset_id}/tags" asset = {"asset": {"tags": tags}} result = client.http_request(message="DELETE", suffix=url_suffix, data=asset) if result.get("status") != "success": return f"Tag {tags} was not deleted from asset {asset_id}.", {}, [] return f"Tag {tags} was successfully deleted from asset {asset_id}.", {}, [] def search_assets_by_external_id_command(client: Client, args: dict[str, str]) -> CommandResults: """ Search for assets by their external ID. This function sends a GET request to the '/assets/search' endpoint with the external ID as a query parameter. If 'to_context' is True, it returns the results in the context. Args: client (Client): The client to use for the HTTP request. args (dict): A dictionary of arguments. Expected keys are 'external_id' (required), 'limit', and 'to_context'. Returns: CommandResults: A CommandResults object. """ external_id = args["external_id"] limit: int = arg_to_number(args.get("limit")) or 500 to_context = argToBoolean(args.get("to_context", False)) url_suffix = f"/assets/search?&q=external_id%3A{external_id}/" human_readable = [] response = client.http_request(message="GET", suffix=url_suffix).get("assets") if not response: return CommandResults(readable_output="No assets were found.", raw_response=response) assets = response[:limit] if len(assets) > limit: demisto.debug(f"found {len(assets)} assets, using the limit arg to keep only the first {limit} ones.") wanted_keys = [ "ID", "Hostname", "Score", "IpAddress", "VulnerabilitiesCount", "OperatingSystem", "Tags", "Fqdn", "Status", "Owner", "Priority", "Notes", "OperatingSystem", ] actual_keys = [ "id", "hostname", "risk_meter_score", "ip_address", "vulnerabilities_count", "operating_system", "tags", "fqdn", "status", "owner", "priority", "notes", "operating_system", ] context: list[dict[str, Any]] = parse_response(assets, wanted_keys, actual_keys) for lst in assets: human_readable.append( { "id": lst.get("id"), "Hostname": lst.get("hostname"), "IP-address": lst.get("ip_address"), "Vulnerabilities Count": args.get("vulnerabilities_count"), "Operating System": lst.get("operating_system"), "Score": lst.get("risk_meter_score"), } ) return CommandResults( outputs_prefix="Kenna.Assets", outputs_key_field="ID", readable_output=tableToMarkdown("Kenna Assets", human_readable, removeNull=True), outputs=context if to_context else None, raw_response=response, ) def main(): command = demisto.command() params = demisto.params() args = demisto.args() api = params.get("credentials_key", {}).get("password") or params.get("key") if not api: raise DemistoException("Kenna API key must be provided.") # Service base URL base_url = params.get("url", "") # Should we use SSL use_ssl = not params.get("insecure", False) # Should we use system proxy settings use_proxy = params.get("proxy", False) # Initialize Client object client = Client(base_url=base_url, api_key=api, verify=use_ssl, proxy=use_proxy) demisto.debug(f"Command being called is {command}") commands: dict[str, Callable[[Client, dict[str, str]], tuple[str, dict[Any, Any], list[Any]]]] = { "kenna-search-vulnerabilities": search_vulnerabilities, "kenna-get-connectors": get_connectors, "kenna-run-connector": run_connector, "kenna-search-fixes": search_fixes, "kenna-update-vulnerability": update_vulnerability, "kenna-get-asset-vulnerabilities": get_asset_vulnerabilities, "kenna-add-tag": add_tags, "kenna-delete-tag": delete_tags, "kenna-get-connector-runs": get_connector_runs, } try: if command in commands: return_outputs(*commands[command](client, args)) elif command == "test-module": return_results(test_module(client)) elif command == "kenna-update-asset": return_results(update_asset_command(client, args)) elif command == "kenna-search-assets": return_results(search_assets_command(client, args)) elif command == "kenna-search-assets-by-external-id": return_results(search_assets_by_external_id_command(client, args)) else: raise NotImplementedError(f"Command {command} is not implemented.") except Exception as err: return_error(f"Failed to execute {command} command.\nError:\n{err!s}") if __name__ in ["__main__", "builtin", "builtins"]: main()