Palo Alto Networks IoT
This is the Palo Alto Networks IoT integration (previously Zingbox).
Network Security · IoT by Palo Alto Networks
Details
| ID | Palo Alto Networks IoT |
|---|---|
| Provider | Palo Alto Networks |
| Category | Network Security |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
This is the Palo Alto Networks IoT integration (previously Zingbox).
This integration was integrated and tested with the Banff release of Palo Alto Networks IoT.
Get your Palo Alto Networks IoT Access Keys
This integration requires that API access be configured.
To obtain the Access Key ID and Secret Access Key, refer to the Palo Alto Networks IoT API User Guide.
Configure Palo Alto Networks IoT in Cortex
| Parameter | Description | Required |
|---|---|---|
| url | Palo Alto Networks IoT Security Portal URL (e.g. https://example.iot.paloaltonetworks.com) | True |
| tenant_id | Tenant ID | True |
| access_key_id | Access Key ID | True |
| secret_access_key | Secret Access Key | True |
| insecure | Trust any certificate (not secure) | False |
| proxy | Use system proxy settings | False |
| first_fetch | First fetch time | False |
| max_fetch | Maximum number of incidents per fetch | False |
| fetch_alerts | Fetch IoT Alerts | False |
| fetch_vulns | Fetch IoT Vulnerabilities | False |
| api_timeout | The timeout for querying APIs | False |
| incidentType | Incident type | False |
| isFetch | Fetch incidents | 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.
iot-security-get-device
IoT get device command - get a single device’s details.
Base Command
iot-security-get-device
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The device uid (mac address) | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| PaloAltoNetworksIoT.Device | unknown | Device details. |
| PaloAltoNetworksIoT.Device.hostname | String | The hostname of the device. |
| PaloAltoNetworksIoT.Device.ip_address | String | The IP address of the device. |
| PaloAltoNetworksIoT.Device.profile_type | String | The device profile type: Non_IoT vs IoT. |
| PaloAltoNetworksIoT.Device.profile_vertical | String | The device profile vertical. |
| PaloAltoNetworksIoT.Device.category | String | The device category |
| PaloAltoNetworksIoT.Device.profile | String | The device profile. |
| PaloAltoNetworksIoT.Device.last_activity | Date | The last activity timestamp of the device. |
| PaloAltoNetworksIoT.Device.long_description | String | The long description of the device. |
| PaloAltoNetworksIoT.Device.vlan | Number | The device VLAN ID. |
| PaloAltoNetworksIoT.Device.site_name | String | The site which the device is in. |
| PaloAltoNetworksIoT.Device.risk_score | Number | The device risk score. |
| PaloAltoNetworksIoT.Device.risk_level | String | The device risk level: Low, Medium, High, Critical |
| PaloAltoNetworksIoT.Device.subnet | String | The device subnet. |
| PaloAltoNetworksIoT.Device.first_seen_date | Date | The first seen date of the device. |
| PaloAltoNetworksIoT.Device.confidence_score | Number | The device confidence score. |
| PaloAltoNetworksIoT.Device.deviceid | Date | The device ID. |
| PaloAltoNetworksIoT.Device.location | String | The device location. |
| PaloAltoNetworksIoT.Device.vendor | String | The device vendor. |
| PaloAltoNetworksIoT.Device.model | String | The device model. |
| PaloAltoNetworksIoT.Device.description | String | The device description. |
| PaloAltoNetworksIoT.Device.asset_tag | String | The device asset tag (e.g. a sticky label at the bottom of the device). |
| PaloAltoNetworksIoT.Device.os_group | String | The device OS group. |
| PaloAltoNetworksIoT.Device.Serial_Number | String | The device serial number. |
| PaloAltoNetworksIoT.Device.DHCP | String | Whether the device is in DHCP model: Valid values are Yes or No. |
| PaloAltoNetworksIoT.Device.wire_or_wireless | String | Is the device wired or wireless. |
| PaloAltoNetworksIoT.Device.department | String | The device department. |
| PaloAltoNetworksIoT.Device.Switch_Port | Number | The port of the switch this device is connected to. |
| PaloAltoNetworksIoT.Device.Switch_Name | String | The name of the switch this device is connected to. |
| PaloAltoNetworksIoT.Device.Switch_IP | String | The IP of the switch this device is connected to. |
| PaloAltoNetworksIoT.Device.Access_Point_IP | String | The IP of the access point this device is connected to. |
| PaloAltoNetworksIoT.Device.Access_Point_Name | String | The name of the access point this device is connected to. |
| PaloAltoNetworksIoT.Device.SSID | String | The SSID of the wireless network this device is connected to. |
| PaloAltoNetworksIoT.Device.MAC | Date | The device MAC address. |
| PaloAltoNetworksIoT.Device.display_tags | String | The user tags of the device. |
| PaloAltoNetworksIoT.Device.mac_address | String | The device MAC address. |
Command Example
iot-security-get-device id=00:0f:e5:04:14:4c
Human Readable Output
| AD_Domain | AD_Username | AET | Access_Point_IP | Access_Point_Name | Applications | Authentication_Method | CMMS_Category | CMMS_Source | CMMS_State | DHCP | EAP_Method | Encryption_Cipher | External_Inventory_Sync_Field | MAC | NAC_Auth_Info | NAC_Auth_State | NAC_profile | NAC_profile_source | NetworkLocation | SMB | SSID | Serial_Number | Source | Switch_IP | Switch_Name | Switch_Port | Synced_With_Third-Party | Time_Synced_With_Third-Party | WIFI_Auth_Status | WIFI_Auth_Timestamp | asset_tag | category | confidence_score | department | description | deviceid | display_tags | endpoint_protection | endpoint_protection_vendor | first_seen_date | hostname | in_use | ip_address | is_server | last_activity | location | long_description | mac_address | model | number_of_caution_alerts | number_of_critical_alerts | number_of_info_alerts | number_of_warning_alerts | os/firmware_version | os_combined | os_group | parent_mac | profile | profile_type | profile_vertical | risk_level | risk_score | services | site_name | source | subnet | vendor | vlan | wire_or_wireless |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 00:0f:e5:04:14:4c | Monitored | Physical Security | 94 | 00:0f:e5:04:14:4c | not_protected | 2020-08-13T07:21:02.000Z | 00:0f:e5:04:14:4c | 10.70.112.20 | 2020-08-18T19:26:05.000Z | 00:0f:e5:04:14:4c | 0 | 0 | 0 | 0 | Access Control Device | IoT | Facility | Low | 10 | test-katherine-0821 | 10.0.0.0/8 | HID Global/Mercury Security |
iot-security-list-devices
IoT list devices command
Base Command
iot-security-list-devices
Input
| Argument Name | Description | Required |
|---|---|---|
| offset | The offset in the pagination. | Optional |
| limit | The maximum size of the list of the devices. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| PaloAltoNetworksIoT.DeviceList | unknown | List of devices. |
Command Example
iot-security-list-devices offset=0 limit=2
Human Readable Output
| AD_Domain | AD_Username | AET | Access_Point_IP | Access_Point_Name | Applications | Authentication_Method | CMMS_Category | CMMS_Source | CMMS_State | DHCP | EAP_Method | Encryption_Cipher | External_Inventory_Sync_Field | MAC | NAC_Auth_Info | NAC_Auth_State | NAC_profile | NAC_profile_source | NetworkLocation | SMB | SSID | Serial_Number | Source | Switch_IP | Switch_Name | Switch_Port | Synced_With_Third-Party | Time_Synced_With_Third-Party | WIFI_Auth_Status | WIFI_Auth_Timestamp | asset_tag | category | confidence_score | department | description | deviceid | display_tags | endpoint_protection | endpoint_protection_vendor | first_seen_date | hostname | in_use | ip_address | is_server | last_activity | location | long_description | mac_address | model | number_of_caution_alerts | number_of_critical_alerts | number_of_info_alerts | number_of_warning_alerts | os/firmware_version | os_combined | os_group | parent_mac | profile | profile_type | profile_vertical | risk_level | risk_score | services | site_name | source | subnet | vendor | vlan | wire_or_wireless |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Monitored | Smartphone | 90 | 356582100001420 | not_protected | 2020-08-11T01:45:31.000Z | 356582100001420 | 1.0.2.2 | 2020-08-11T00:09:02.000Z | 356582100001420 | iPhone 11 (A2223) | 0 | 0 | 0 | 0 | iOS | iOS | Apple iPhone 11 (A2223) | IoT | Traditional IT | Low | 21 | test | uknown | ||||||||||||||||||||||||||||||||||||||||||||||
| Monitored | Smartphone | 90 | 356582100001430 | not_protected | 2020-08-11T01:48:05.000Z | 356582100001430 | 1.0.3.2 | 2020-08-11T00:09:02.000Z | 356582100001430 | iPhone 11 (A2223) | 0 | 0 | 0 | 0 | iOS | iOS | Apple iPhone 11 (A2223) | IoT | Traditional IT | Low | 21 | test | uknown |
iot-security-list-alerts
IoT list alerts.
Base Command
iot-security-list-alerts
Input
| Argument Name | Description | Required |
|---|---|---|
| start_time | The start time in the format of ISO 8601 in UTC, e.g. 2018-11-06T08:56:41Z. | Optional |
| offset | The offset in the pagination. | Optional |
| limit | The maximum size of the list of the alerts. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| PaloAltoNetworksIoT.Alerts | unknown | List of alerts. |
Command Example
iot-security-list-alerts offset=0 limit=2
Human Readable Output
| category | date | description | deviceid | hostname | id | inspectorid | internal_hostname | msg | name | profile | reason_history | resolved | serviceLevel | severity | severityNumber | siteid | tenantid | type | zb_ticketid |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Network Security Equipment | 2020-08-26T06:11:04.000Z | The usage of an outdated Chrome version has been detected on this device. Using older versions of a web browser can expose your device to security risks. | d4:f4:be:b0:c3:10 | 5f463c8703a2260700a99dbf | 012501000732 | severity: low<br>taggedBy: PolicyAlert<br>userPolicy: false<br>alertType: security risk<br>localDeviceRole: initiator<br>values: {‘label’: ‘user agent’, ‘value’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19041’}<br>localProfile: Palo Alto Networks Device<br>description: The usage of an outdated Chrome version has been detected on this device. Using older versions of a web browser can expose your device to security risks.<br>recommendation: {“content”: [“Update the browser to the latest version”, “If browser usage on the device is authorized and essential, use a URL-filtering tool to block connections to known malicious websites or update firewall policy rules to permit connections only to designated websites.”, “Check network traffic coming to and from the device on the device details page and enable trusted behavior by applying an ACL (access control list) to restrict nonessential traffic.”]}<br>alertKey: 24072002d4:f4:be:b0:c3:10analytics-outdated-chrome<br>anomalyMap: {“application”: 1}<br>generationTimestamp: 1598438532757<br>autoPublish: true<br>name: Outdated Chrome version used by IoT device<br>localip: 192.168.58.56<br>fromip: 192.168.58.56<br>id: ObDMsWG0<br>ruleid: analytics-outdated-chrome<br>status: publish<br>toURL: UNKNOWN URL<br>hostname: unknown | Outdated Chrome version used by IoT device | Palo Alto Networks Device | no | low | 2 | 0 | policy_alert | alert-ObDMsWG0 | |||||
| IT Server | 2020-08-26T02:09:43.000Z | This event indicates a brute force attack through multiple login attempts to an SSH server. | 00:25:90:92:82:2a | 5f45c4a52f31500800a47fc7 | 012501003437 | taggedBy: PolicyAlert<br>values: {‘label’: ‘device profile’, ‘value’: ‘Super Micro Computer’},<br>{‘label’: ‘client port’, ‘value’: 34904},<br>{‘label’: ‘threat ID’, ‘value’: 40015},<br>{‘label’: ‘threat category’, ‘value’: ‘brute-force’},<br>{‘label’: ‘threat type’, ‘value’: ‘vulnerability’},<br>{‘label’: ‘number of occurrences’, ‘value’: 2},<br>{‘label’: ‘alert source’, ‘value’: ‘Firewall’},<br>{‘label’: ‘firewall name’, ‘value’: ‘SJC-Eng-5260-fw1’},<br>{‘label’: ‘firewall action’, ‘value’: ‘Raised an alert’},<br>{‘label’: ‘firewall inbound interface’, ‘value’: ‘vlan’},<br>{‘label’: ‘firewall outbound interface’, ‘value’: ‘vlan’}<br>localProfile: Super Micro Computer<br>localDeviceLabels: Attacker<br>description: This event indicates a brute force attack through multiple login attempts to an SSH server.<br>recommendation: {“content”: [“Enable brute-force login protection by setting a maximum limit for the number of unsuccessful login attempts the device will accept before refusing further attempts.”, “If unauthorized users tried to log in, block the IP addresses from which they made their attempts.”, “Avoid using the manufacturer’s default credentials or the same text string as both the username and password.”, “Strengthen the login username and password for the ssh application.”]}<br>anomalyMap: {“payload”: 2}<br>generationTimestamp: 1598407836500<br>remoteHostMetadata: {‘deviceIds’: [‘10.0.16.245’], ‘ip’: ‘10.0.16.245’, ‘connections’: [{‘app’: ‘ssh’, ‘port’: 22, ‘ipProto’: ‘tcp’}], ‘network’: ‘internal’}<br>toip: 10.0.16.245<br>fromip: 10.0.6.174<br>id: KbYbFjYw<br>severity: medium<br>threatid: 40015<br>userPolicy: false<br>alertType: vulnerability<br>localDeviceRole: initiator<br>appName: ssh<br>alertKey: 2407200200:25:90:92:82:2aanalytics-evt-threat-attacker40015<br>remoteHostLabels: Victim<br>autoPublish: true<br>isAttempt: false<br>forensicData: {“search”: {“iotdevid”: “00:25:90:92:82:2a”, “threatid”: 40015, “remoteIPAddr”: [“10.0.16.245”], “appName”: “ssh”, “tenantid”: “24072002”, “isClient”: “Yes”, “reverse”: true, “timestamp”: 1598407783000, “isLocal”: true, “direction”: “client to server”}, “addFields”: {“rxPkts”: “packets”, “txPkts”: “packets”}}<br>name: SSH User Authentication Brute Force Attempt<br>localip: 10.0.6.174<br>threatCategory: brute-force<br>ruleid: analytics-evt-threat-attacker<br>status: publish<br>toURL: UNKNOWN URL<br>hostname: unknown | SSH User Authentication Brute Force Attempt | Super Micro Computer | no | medium | 3 | 0 | policy_alert | alert-KbYbFjYw |
iot-security-list-vulns
IoT list Vulnerabilities.
Base Command
iot-security-list-vulns
Input
| Argument Name | Description | Required |
|---|---|---|
| start_time | The start time in the format of ISO 8601 in UTC, e.g. 2018-11-06T08:56:41Z. | Optional |
| offset | The offset in the pagination. | Optional |
| limit | The maximum size of the list of the vulnerabilities. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| PaloAltoNetworksIoT.Vulns | unknown | List of vulnerabilities. |
Command Example
iot-security-list-vulns limit=2 offset=0
Human Readable Output
| asset_tag | date | detected_date | deviceid | display_profile_category | ip | model | name | os | osCombined | profile | profile_vertical | reason_history | remediate_checkbox | remediate_instruction | remediate_workorder | risk_level | risk_score | siteName | siteid | sn | ticketAssignees | ticketState | vendor | vulnerability_name | zb_ticketid |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2020-07-16T09:18:21.000Z | 2020-08-20T23:59:59.000Z | 64:16:7f:77:45:c9 | Video Audio Conference | 10.72.32.237 | Trio8800 | Polycom_64167f7745c9 | Embedded | Embedded | Polycom Video Conferencing Device | Office | Low | 26 | test | 0 | Polycom | Vulnerability Test - Medium | vuln-65046ad8 | ||||||||
| 2020-07-22T19:18:32.000Z | 2020-08-20T23:59:59.000Z | 64:16:7f:76:64:c6 | Video Audio Conference | 10.72.33.195 | Trio8800 | Polycom_64167f7664c6 | Embedded | Embedded | Polycom Device | Office | Low | 26 | test | 0 | Polycom | Vulnerability Test - Medium | vuln-8cc12cd4 |
iot-security-resolve-alert
Resolving an IoT alert.
Base Command
iot-security-resolve-alert
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The alert ID | Required |
| reason | The alert resolution reason. | Optional |
| reason_type | The alert resolution reason type (No Action Needed, Issue Mitigated). | Optional |
Context Output
There is no context output for this command.
Command Example
iot-security-resolve-alert id="5e73ecb3eff46f80a7cdc57a" reason=test reason_type="No Action Needed"
iot-security-resolve-vuln
Resolving an IoT vulnerability.
Base Command
iot-security-resolve-vuln
Input
| Argument Name | Description | Required |
|---|---|---|
| id | The vulnerability ID. | Required |
| full_name | The vulnerability full name. | Required |
| reason | The vulnerability resolution reason. | Optional |
Context Output
There is no context output for this command.
Command Example
iot-security-resolve-vuln full_name=CVE-2019-10960 id=vuln-b12d4f0a reason=test
iot-security-get-device-by-ip
IoT get device command - get a single device’s details.
Base Command
iot-security-get-device-by-ip
Input
| Argument Name | Description | Required |
|---|---|---|
| ip | The device ip (ip address). | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| PaloAltoNetworksIoT.Device | unknown | Device details. |
| PaloAltoNetworksIoT.Device.hostname | String | The hostname of the device. |
| PaloAltoNetworksIoT.Device.ip_address | String | The IP address of the device. |
| PaloAltoNetworksIoT.Device.profile_type | String | The device profile type: Non_IoT vs IoT. |
| PaloAltoNetworksIoT.Device.profile_vertical | String | The device profile vertical. |
| PaloAltoNetworksIoT.Device.category | String | The device category |
| PaloAltoNetworksIoT.Device.profile | String | The device profile. |
| PaloAltoNetworksIoT.Device.last_activity | Date | The last activity timestamp of the device. |
| PaloAltoNetworksIoT.Device.long_description | String | The long description of the device. |
| PaloAltoNetworksIoT.Device.vlan | Number | The device VLAN ID. |
| PaloAltoNetworksIoT.Device.site_name | String | The site which the device is in. |
| PaloAltoNetworksIoT.Device.risk_score | Number | The device risk score. |
| PaloAltoNetworksIoT.Device.risk_level | String | The device risk level: Low, Medium, High, Critical |
| PaloAltoNetworksIoT.Device.subnet | String | The device subnet. |
| PaloAltoNetworksIoT.Device.first_seen_date | Date | The first seen date of the device. |
| PaloAltoNetworksIoT.Device.confidence_score | Number | The device confidence score. |
| PaloAltoNetworksIoT.Device.deviceid | Date | The device ID. |
| PaloAltoNetworksIoT.Device.location | String | The device location. |
| PaloAltoNetworksIoT.Device.vendor | String | The device vendor. |
| PaloAltoNetworksIoT.Device.model | String | The device model. |
| PaloAltoNetworksIoT.Device.description | String | The device description. |
| PaloAltoNetworksIoT.Device.asset_tag | String | The device asset tag (e.g. a sticky label at the bottom of the device). |
| PaloAltoNetworksIoT.Device.os_group | String | The device OS group. |
| PaloAltoNetworksIoT.Device.Serial_Number | String | The device serial number. |
| PaloAltoNetworksIoT.Device.DHCP | String | Whether the device is in DHCP model: Valid values are Yes or No. |
| PaloAltoNetworksIoT.Device.wire_or_wireless | String | Is the device wired or wireless. |
| PaloAltoNetworksIoT.Device.department | String | The device department. |
| PaloAltoNetworksIoT.Device.Switch_Port | Number | The port of the switch this device is connected to. |
| PaloAltoNetworksIoT.Device.Switch_Name | String | The name of the switch this device is connected to. |
| PaloAltoNetworksIoT.Device.Switch_IP | String | The IP of the switch this device is connected to. |
| PaloAltoNetworksIoT.Device.Access_Point_IP | String | The IP of the access point this device is connected to. |
| PaloAltoNetworksIoT.Device.Access_Point_Name | String | The name of the access point this device is connected to. |
| PaloAltoNetworksIoT.Device.SSID | String | The SSID of the wireless network this device is connected to. |
| PaloAltoNetworksIoT.Device.MAC | Date | The device MAC address. |
| PaloAltoNetworksIoT.Device.display_tags | String | The user tags of the device. |
| PaloAltoNetworksIoT.Device.mac_address | String | The device MAC address. |
Configuration parameters
url— Palo Alto Networks IoT Security Portal URL (e.g. https://example.iot.paloaltonetworks.com) (required)tenant_id— Tenant ID (required)credentials— Access Key IDaccess_key_id— Access Key IDsecret_access_key— Secret Access Keyinsecure— Trust any certificate (not secure)proxy— Use system proxy settingsfirst_fetch— First fetch timemax_fetch— Maximum number of incidents per fetchfetch_alerts— Fetch IoT Alertsfetch_vulns— Fetch IoT Vulnerabilitiesapi_timeout— The timeout for querying APIsincidentType— Incident typeincidentFetchInterval— Incidents Fetch IntervalisFetch— Fetch incidents
Commands (7)
-
iot-security-get-deviceIoT get device command - get a single device's details.
-
iot-security-get-device-by-ipIoT get device command - get a single device's details.
-
iot-security-list-alertsIoT list alerts.
-
iot-security-list-devicesIoT list devices command.
-
iot-security-list-vulnsIoT list Vulnerabilities.
-
iot-security-resolve-alertResolving an IoT alert.
-
iot-security-resolve-vulnResolving an IoT vulnerability.
import json import time from datetime import UTC, datetime from typing import Any import dateparser import demistomock as demisto # noqa: F401 import urllib3 from CommonServerPython import * # noqa: F401 from CommonServerUserPython import * # noqa: E402 lgtm [py/polluting-import] # IMPORTS # Disable insecure warnings urllib3.disable_warnings() # CONSTANTS # api list size limit PAGELENGTH = 100 class Client(BaseClient): """ Client will implement the service API, and should not contain any Demisto logic. Should only do requests and return data. """ def __init__( self, base_url, tenant_id, first_fetch="-1", max_fetch=10, api_timeout=60, verify=True, proxy=False, ok_codes=(), headers=None, ): super().__init__(base_url, verify=verify, proxy=proxy, ok_codes=ok_codes, headers=headers) self.tenant_id = tenant_id self.api_timeout = api_timeout self.first_fetch = first_fetch self.max_fetch = min(max_fetch, PAGELENGTH) def _http_request(self, **kwargs): # type: ignore[override] try: return super()._http_request(**kwargs) except DemistoException as error: error_message = error.args[0] if "[404]" in error_message: ind = error_message.find("Not Found") new_message = error_message[:ind] + "\nValidate your server url address" raise DemistoException(new_message) elif "[403]" in error_message: ind = error_message.find("Forbidden") new_message = error_message[:ind] + "\nValidate your Tenant ID, Access Key ID or Secret Access Key " raise DemistoException(new_message) else: raise error def get_device(self, id): """ Get a device from IoT security portal by device ID """ return self._http_request( method="GET", url_suffix="/device", params={"customerid": self.tenant_id, "deviceid": id}, timeout=self.api_timeout ) def get_device_by_ip(self, ip): """ Get a device from IoT security portal by ip """ return self._http_request( method="GET", url_suffix="/device/ip", params={"customerid": self.tenant_id, "ip": ip}, timeout=self.api_timeout ) def list_alerts(self, stime="-1", offset=0, pagelength=100, sortdirection="asc"): """ returns alerts inventory list """ data = self._http_request( method="GET", url_suffix="/alert/list", params={ "customerid": self.tenant_id, "offset": offset, "pagelength": pagelength, "stime": stime, "type": "policy_alert", "resolved": "no", "sortfield": "date", "sortdirection": sortdirection, }, timeout=self.api_timeout, ) return data["items"] def list_vulns(self, stime="-1", offset=0, pagelength=100): """ returns vulnerability instances """ data = self._http_request( method="GET", url_suffix="/vulnerability/list", params={ "customerid": self.tenant_id, "offset": offset, "pagelength": pagelength, "stime": stime, "type": "vulnerability", "status": "Confirmed", "groupby": "device", }, timeout=self.api_timeout, ) return data["items"] def list_devices(self, offset, pagelength): """ returns a list of devices """ data = self._http_request( method="GET", url_suffix="/device/list", params={ "customerid": self.tenant_id, "filter_monitored": "no", "offset": offset, "pagelength": pagelength, "stime": f"{datetime.utcfromtimestamp(int(time.time()) - 2592000).isoformat()}Z", "detail": "true", "sortfield": "MAC", "sortdirection": "asc", }, timeout=self.api_timeout, ) return data["devices"] def resolve_alert(self, alert_id, reason, reason_type="No Action Needed"): """ resolve an IoT alert """ return self._http_request( method="PUT", url_suffix="/alert/update", params={"customerid": self.tenant_id, "id": alert_id}, json_data={"resolved": "yes", "reason": reason, "reason_type": [reason_type]}, timeout=self.api_timeout, ) def resolve_vuln(self, vuln_id, full_name, reason): """ resolve an IoT vulnerability """ return self._http_request( method="PUT", url_suffix="/vulnerability/update", params={"customerid": self.tenant_id}, json_data={"action": "mitigate", "full_name": full_name, "reason": reason, "ticketIdList": [vuln_id]}, timeout=self.api_timeout, ) def arg_to_timestamp(arg: Any, arg_name: str, required: bool = False) -> int | None: """Converts an XSOAR argument to a timestamp (seconds from epoch) This function is used to quickly validate an argument provided to XSOAR via ``demisto.args()`` into an ``int`` containing a timestamp (seconds since epoch). It will throw a ValueError if the input is invalid. If the input is None, it will throw a ValueError if required is ``True``, or ``None`` if required is ``False. :type arg: ``Any`` :param arg: argument to convert :type arg_name: ``str`` :param arg_name: argument name :type required: ``bool`` :param required: throws exception if ``True`` and argument provided is None :return: returns an ``int`` containing a timestamp (seconds from epoch) if conversion works returns ``None`` if arg is ``None`` and required is set to ``False`` otherwise throws an Exception :rtype: ``Optional[int]`` """ if arg is None: if required is True: raise ValueError(f'Missing "{arg_name}"') return None if isinstance(arg, str) and arg.isdigit(): # timestamp is a str containing digits - we just convert it to int return int(arg) if isinstance(arg, str): # we use dateparser to handle strings either in ISO8601 format, or # relative time stamps. # For example: format 2019-10-23T00:00:00 or "3 days", etc date = dateparser.parse(arg, settings={"TIMEZONE": "UTC"}) if date is None: # if d is None it means dateparser failed to parse it raise ValueError(f"Invalid date: {arg}") return int(date.replace(tzinfo=UTC).timestamp()) if isinstance(arg, int | float): # Convert to int if the input is a float return int(arg) raise ValueError(f'Invalid date: "{arg}"') def test_module(client): """ Returning 'ok' indicates that the integration works like it is supposed to. Connection to the service is successful. Args: client: IoT client Returns: 'ok' if test passed, anything else will fail the test. """ if demisto.params().get("isFetch"): fetch_incidents(client, last_run=demisto.getLastRun(), is_test=True) else: client.list_devices(0, 1) return "ok" def iot_get_device(client, args): """ Returns an IoT device Args: client (Client): IoT client. args (dict): all command arguments. Returns: device CommandResults """ device_id = args.get("id") result = client.get_device(device_id) return CommandResults(outputs_prefix="PaloAltoNetworksIoT.Device", outputs_key_field="deviceid", outputs=result) def iot_get_device_by_ip(client, args): """ Returns an IoT device Args: client (Client): IoT client. args (dict): all command arguments. Returns: device CommandResults """ device_ip = args.get("ip") result = client.get_device_by_ip(device_ip) return CommandResults(outputs_prefix="PaloAltoNetworksIoT.Device", outputs_key_field="devices", outputs=result["devices"]) def iot_list_devices(client, args): """ Returns a list of IoT devices Args: client (Client): IoT client. args (dict): all command arguments. Returns: List of devices CommandResults """ offset = args.get("offset", "0") pagelength = args.get("limit", client.max_fetch) result = client.list_devices(offset, pagelength) if not result: return CommandResults(readable_output="### No devices found") return CommandResults(outputs_prefix="PaloAltoNetworksIoT.DeviceList", outputs_key_field="deviceid", outputs=result) def iot_list_alerts(client, args): """ Returns a list of IoT alerts (max: 1000) Args: client (Client): IoT client. args (dict): all command arguments. Returns: List of alerts CommandResults """ stime = args.get("start_time", "-1") offset = args.get("offset", 0) pagelength = min(int(args.get("limit", client.max_fetch)), PAGELENGTH) result = client.list_alerts(stime, offset, pagelength, "desc") if not result: return CommandResults(readable_output="### No alerts found") return CommandResults(outputs_prefix="PaloAltoNetworksIoT.Alerts", outputs_key_field="id", outputs=result) def iot_list_vulns(client, args): """ Returns a list of IoT vulnerabilties (max: 1000) Args: client (Client): IoT client. args (dict): all command arguments. Returns: List of vulnerabilties CommandResults """ stime = args.get("start_time", "-1") offset = args.get("offset", 0) pagelength = min(int(args.get("limit", client.max_fetch)), PAGELENGTH) result = client.list_vulns(stime, offset, pagelength) if not result: return CommandResults(readable_output="### No vulnerabilities found") return CommandResults(outputs_prefix="PaloAltoNetworksIoT.Vulns", outputs_key_field="zb_ticketid", outputs=result) def iot_resolve_alert(client, args): """ Resolve an IoT alert Args: client (Client): IoT client. args (dict): all command arguments. Returns: None in CommandResults """ alert_id = args.get("id") reason = args.get("reason", "resolved by XSOAR") reason_type = args.get("reason_type", "No Action Needed") client.resolve_alert(alert_id, reason, reason_type) return CommandResults(readable_output=f"Alert {alert_id} was resolved successfully") def iot_resolve_vuln(client, args): """ Resolve an IoT vulnerability Args: client (Client): IoT client. args (dict): all command arguments. Returns: None in CommandResults """ vuln_id = args.get("id") full_name = args.get("full_name") reason = args.get("reason", "resolved by XSOAR") client.resolve_vuln(vuln_id, full_name, reason) return CommandResults(readable_output=f"Vulnerability {vuln_id} was resolved successfully") def fetch_incidents(client, last_run, is_test=False): """ This function will execute each interval (default is 1 minute). Args: client (Client): IoT client last_run: last_run dict containing the timestamps of the latest incident we fetched from previous fetch Returns: next_run: This will be last_run in the next fetch-incidents incidents: Incidents that will be created in Demisto """ demisto.debug("PaloAltoNetworks_IoT - Start fetching") demisto.debug(f"PaloAltoNetworks_IoT - Last run: {json.dumps(last_run)}") # Get the last fetch time, if exists last_alerts_fetch = last_run.get("last_alerts_fetch") last_vulns_fetch = last_run.get("last_vulns_fetch") max_fetch = client.max_fetch incidents = [] if demisto.params().get("fetch_alerts", True): stime = client.first_fetch if last_alerts_fetch is not None: # need to add 1ms for the stime stime = datetime.utcfromtimestamp(last_alerts_fetch + 0.001).isoformat() + "Z" alerts = client.list_alerts(stime, pagelength=max_fetch) demisto.debug(f"PaloAltoNetworks_IoT - Number of incidents- alerts before filtering: {len(alerts)}") # special handling for the case of having more than the pagelength if len(alerts) == max_fetch: # get the last date last_date = alerts[-1]["date"] offset = 0 done = False while not done: offset += max_fetch others = client.list_alerts(stime, offset, pagelength=max_fetch) for alert in others: if alert["date"] == last_date: alerts.append(alert) else: done = True break if len(others) != max_fetch: break for alert in alerts: alert_date_epoch = datetime.strptime(alert["date"], "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=UTC).timestamp() alert_id = alert["zb_ticketid"].replace("alert-", "") incident = { "name": alert["name"], "type": "IoT Alert", "occurred": alert["date"], "rawJSON": json.dumps(alert), "details": alert.get("description", ""), "CustomFields": {"iotincidenturl": f'{demisto.params()["url"]}/guardian/policies/alert?id={alert_id}'}, } incidents.append(incident) # Update last run and add incident if the incident is newer than last fetch if last_alerts_fetch is None or alert_date_epoch > last_alerts_fetch: last_alerts_fetch = alert_date_epoch if demisto.params().get("fetch_vulns", True): stime = client.first_fetch if last_vulns_fetch is not None: # need to add 1ms for the stime stime = datetime.utcfromtimestamp(last_vulns_fetch + 0.001).isoformat() + "Z" vulns = client.list_vulns(stime, pagelength=max_fetch) # special handling for the case of having more than the pagelength if len(vulns) == max_fetch: # get the last date last_date = vulns[-1]["detected_date"] if last_date and isinstance(last_date, list): last_date = last_date[0] offset = 0 done = False while not done: offset += max_fetch others = client.list_vulns(stime, offset, pagelength=max_fetch) for vuln in others: detected_date = vuln["detected_date"] if detected_date and isinstance(detected_date, list): detected_date = detected_date[0] if detected_date == last_date: vulns.append(vuln) else: done = True break if len(others) != max_fetch: break demisto.debug(f"PaloAltoNetworks_IoT - Number of incidents- vulnerability before filtering: {len(vulns)}") for vuln in vulns: detected_date = vuln["detected_date"] if detected_date and isinstance(detected_date, list): detected_date = detected_date[0] vuln_date_epoch = datetime.strptime(detected_date, "%Y-%m-%dT%H:%M:%S.%fZ").replace(tzinfo=UTC).timestamp() vuln_name_encoded = vuln["vulnerability_name"].replace(" ", "+") incident = { "name": vuln["name"], "type": "IoT Vulnerability", "occurred": detected_date, "rawJSON": json.dumps(vuln), "details": f'Device {vuln["name"]} at IP {vuln["ip"]}: {vuln["vulnerability_name"]}', "CustomFields": { "iotincidenturl": f'{demisto.params()["url"]}/guardian/monitor/inventory/device/' f'{vuln["deviceid"]}?index=0&vuln=true&vulname={vuln_name_encoded}' }, } incidents.append(incident) if last_vulns_fetch is None or vuln_date_epoch > last_vulns_fetch: last_vulns_fetch = vuln_date_epoch next_run = {"last_alerts_fetch": last_alerts_fetch, "last_vulns_fetch": last_vulns_fetch} demisto.debug(f"PaloAltoNetworks_IoT - Number of incidents (alerts and vulnerability) after filtering : {len(incidents)}") demisto.debug(f"PaloAltoNetworks_IoT - Next run after incidents fetching: {json.dumps(next_run)}") if is_test: return None, None return next_run, incidents def main(): """ PARSE AND VALIDATE INTEGRATION PARAMS """ tenant_id = demisto.params()["tenant_id"] access_key_id = demisto.params().get("credentials", {}).get("identifier") or demisto.params().get("access_key_id") secret_access_key = demisto.params().get("credentials", {}).get("password") or demisto.params().get("secret_access_key") api_timeout = 60 try: api_timeout = int(demisto.params().get("api_timeout", "60")) except ValueError: return_error("API timeout needs to be an integer") first_fetch = "-1" try: ff = arg_to_timestamp(arg=demisto.params().get("first_fetch"), arg_name="First fetch time", required=False) if ff: first_fetch = datetime.fromtimestamp(ff).astimezone(UTC).strftime("%Y-%m-%dT%H:%M:%SZ") except ValueError as e: return_error(f"First fetch time is in a wrong format. Error: {e!s}") max_fetch = 10 try: max_fetch = int(demisto.params().get("max_fetch", "10")) except ValueError: return_error("Maximum number of incidents per fetch needs to be an integer") # get the service API url base_url = urljoin(demisto.params()["url"], "/pub/v4.0") verify_certificate = not demisto.params().get("insecure", False) proxy = demisto.params().get("proxy", False) demisto.info(f"Command being called is {demisto.command()}") try: client = Client( base_url=base_url, tenant_id=tenant_id, api_timeout=api_timeout, first_fetch=first_fetch, max_fetch=max_fetch, verify=verify_certificate, proxy=proxy, ok_codes=(200,), headers={"X-Key-Id": access_key_id, "X-Access-Key": secret_access_key}, ) if demisto.command() == "test-module": # This is the call made when pressing the integration Test button. result = test_module(client) demisto.results(result) elif demisto.command() == "fetch-incidents": # Set and define the fetch incidents command to run after activated via integration settings. next_run, incidents = fetch_incidents(client=client, last_run=demisto.getLastRun()) if next_run is not None: demisto.setLastRun(next_run) if incidents is not None: demisto.incidents(incidents) elif demisto.command() == "iot-security-get-device": return_results(iot_get_device(client, demisto.args())) elif demisto.command() == "iot-security-get-device-by-ip": return_results(iot_get_device_by_ip(client, demisto.args())) elif demisto.command() == "iot-security-list-devices": return_results(iot_list_devices(client, demisto.args())) elif demisto.command() == "iot-security-list-alerts": return_results(iot_list_alerts(client, demisto.args())) elif demisto.command() == "iot-security-list-vulns": return_results(iot_list_vulns(client, demisto.args())) elif demisto.command() == "iot-security-resolve-alert": return_results(iot_resolve_alert(client, demisto.args())) elif demisto.command() == "iot-security-resolve-vuln": return_results(iot_resolve_vuln(client, demisto.args())) # Log exceptions except Exception as e: return_error(f"Failed to execute {demisto.command()} command. Error: {e!s}") if __name__ in ("__main__", "__builtin__", "builtins"): main()