ExtraHop v2
ExtraHop Reveal(x) for Cortex XSOAR is a network detection and response solution that provides complete visibility of network communications at enterprise scale, real-time threat detections backed by machine learning, and guided investigation workflows that simplify response.
Network Security · ExtraHop Reveal(x)
Details
| ID | ExtraHop v2 |
|---|---|
| Provider | Bain Capital Private Equity |
| Category | Network Security |
| From Version | 6.5.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM EDR Cortex Cloud Cloud Runtime Security |
README
ExtraHop Reveal(x) for Cortex XSOAR is a network detection and response solution that provides complete visibility of network communications at enterprise scale, real-time threat detections backed by machine learning, and guided investigation workflows that simplify response.
Configure ExtraHop Reveal(x) in Cortex
- Navigate to Settings > Integrations > Servers & Services.
- Search for ExtraHop Reveal(x).
- Click Add instance to create and configure a new integration instance.
| Parameter | Description | Required |
|---|---|---|
| Name | The name of the instance. | True |
| Fetches incidents | Select to enable this instance to fetch detection events. Otherwise, select Do not fetch. Each API call fetches a maximum of 200 detection events. | True |
| Classifier | Specifies the type of incident to be created for detection events ingested by this instance. | False |
| Incident type | Specifies the type of incident to be created for detection events ingested by this instance if a Classifier is not specified. | False |
| Mapper | Specifies how detection events ingested by this instance are mapped to Cortex XSOAR incident fields. | False |
| On Cloud | The type of ExtraHop system the integration will connect to. Select if connecting to ExtraHop Reveal(x) 360. Leave unselected if connecting to Reveal(x) Enterprise. | False |
| URL | The URL of the ExtraHop system this integration will connect to. | True |
| API Key | The API key required for authentication if connecting to ExtraHop Reveal(x) Enterprise. The API key is generated on your ExtraHop system. | False |
| Client ID and Client Secret | The credential pair required for authentication if connecting to ExtraHop Reveal(x) 360. The client ID and secret are generated on your ExtraHop system. | False |
| Trust any certificate (not secure) | Specifies whether to allow connections without verifying SSL certificate’s validity. | False |
| Use system proxy settings | Specifies whether to use XSOAR system proxy settings to connect to the API. | False |
| First fetch time | Specifies the beginning timestamp from which to start fetching detection events. | False |
| Incidents Fetch Interval | Specifies how often the instance fetches detection events. Because each API call fetches a maximum of 200 detection events, we recommend specifying one minute intervals to fetch all detection events. | False |
| Advanced Filter | Applies a filter to the list of detections based on a JSON-specific query. Example for detections: { “categories”: [“sec.attack”], “risk_score_min”: 51 } If the categories and category are not specified, then categories will be set to [“sec.attack”]. The category field is deprecated by the API, so please use the categories field instead. For a complete reference to the Extrahop detections filter fields, please refer to the ExtraHop REST API documentation at https://docs.extrahop.com/current/rest-api-guide/ |
False |
| Do not use by default | Select to disable running commands through the Cortex XSOAR CLI on this instance of the integration. | False |
| Log Level | Specifies the level of logging to enable for this instance of the integration. | False |
| Run on | Specifies whether to run the instance of the integration on a single engine. | False |
- Click Test to validate the URL, credentials, and connection.
Commands
You can run the following commands from the Cortex XSOAR CLI, as part of an automation, or in a playbook.
After you successfully run a command, a DBot message appears in the War Room with the command details.
- Get detections from ExtraHop Reveal(x): extrahop-detections-list
- Link an ExtraHop Reveal(x) detection to a Cortex XSOAR incident: extrahop-ticket-track
- Search for devices in ExtraHop Reveal(x): extrahop-devices-search
- Get all active network protocols for a device from ExtraHop Reveal(x): extrahop-protocols-get
- Get all peers for a device from ExtraHop Reveal(x): extrahop-peers-get
- Get a link to a live activity map in ExtraHop Reveal(x): extrahop-activity-map-get
- Get all devices on the Advanced Analysis watchlist in ExtraHop Reveal(x): extrahop-watchlist-get
- Add or remove devices from the Advanced Analysis watchlist in ExtraHop Reveal(x): extrahop-watchlist-edit
- Add or remove a tag from devices in ExtraHop Reveal(x): extrahop-devices-tag
- Get all alert rules from ExtraHop Reveal(x): extrahop-alert-rules-get
- Create a new alert rule in ExtraHop Reveal(x): extrahop-alert-rule-create
- Modify an alert rule in ExtraHop Reveal(x): extrahop-alert-rule-edit
- Get metrics for specified objects from ExtraHop Reveal(x): extrahop-metrics-list
- Search for specific packets in ExtraHop Reveal(x): extrahop-packets-search
extrahop-watchlist-get
Get all devices on the advanced analysis watchlist in ExtraHop Reveal(x).
Base Command
extrahop-watchlist-get
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| ExtraHop.Device.Macaddr | String | The MAC Address of the device. |
| ExtraHop.Device.DeviceClass | String | The class of this device. |
| ExtraHop.Device.UserModTime | Number | The time of the most recent update, expressed in milliseconds since the epoch. |
| ExtraHop.Device.AutoRole | String | The role automatically detected by the ExtraHop system. |
| ExtraHop.Device.ParentId | Number | The ID of the parent device. |
| ExtraHop.Device.Vendor | String | The device vendor. |
| ExtraHop.Device.Analysis | string | The level of analysis received by the device. |
| ExtraHop.Device.DiscoveryId | String | The UUID for this device. |
| ExtraHop.Device.DefaultName | String | The default name for this device. |
| ExtraHop.Device.DisplayName | String | The display name of device. |
| ExtraHop.Device.OnWatchlist | Boolean | Whether the device is on the advanced analysis watch list. |
| ExtraHop.Device.ModTime | Number | The time of the most recent update, expressed in milliseconds since the epoch. |
| ExtraHop.Device.IsL3 | Boolean | Indicates whether the device is a layer 3 device. |
| ExtraHop.Device.Role | String | The role of the device. |
| ExtraHop.Device.DiscoverTime | Number | The time that the device was discovered. |
| ExtraHop.Device.Id | Number | The ID of the device. |
| ExtraHop.Device.Ipaddr4 | String | The IPv4 address for this device. |
| ExtraHop.Device.Vlanid | Number | The unique identifier for the VLAN associated with the device. |
| ExtraHop.Device.Ipaddr6 | string | The IPv6 address of the device. |
| ExtraHop.Device.NodeId | number | The node ID of the sensor associated with this device. |
| ExtraHop.Device.Description | string | A user customizable description of the device. |
| ExtraHop.Device.DnsName | string | The DNS name associated with the device. |
| ExtraHop.Device.DhcpName | string | The DHCP name associated with the device. |
| ExtraHop.Device.CdpName | string | The Cisco Discovery Protocol name associated with the device. |
| ExtraHop.Device.NetbiosName | string | The NetBIOS name associated with the device. |
| ExtraHop.Device.Url | string | Link to the device details page in ExtraHop Reveal(x). |
Command example
#### Context Example
```json
{
"ExtraHop": {
"Device": [
{
"analysis": "advanced",
"analysis_level": 2,
"auto_role": "other",
"critical": false,
"default_name": "VM9",
"device_class": "node",
"dhcp_name": "test",
"discover_time": 1635499650000,
"discovery_id": "0000000000000000",
"display_name": "test",
"extrahop_id": "0000000000000000",
"id": 25769803982,
"ipaddr4": "0.0.0.0",
"is_l3": false,
"macaddr": "00:00:00:00:00:00",
"mod_time": 1676638611398,
"model": "vmware_vm",
"node_id": 6,
"on_watchlist": true,
"role": "other",
"url": "https://dummy_url/extrahop/#/metrics/devices/overview/",
"user_mod_time": 1676290306316,
"vendor": "VMware",
"vlanid": 0
},
{
"analysis": "advanced",
"analysis_level": 2,
"auto_role": "other",
"critical": false,
"default_name": "VM8",
"device_class": "node",
"discover_time": 1675318050000,
"discovery_id": "0000000000000000",
"display_name": "VM8",
"extrahop_id": "0000000000000000",
"id": 25769808133,
"ipaddr4": "0.0.0.0",
"is_l3": false,
"last_seen_time": 1675319010000,
"macaddr": "00:00:00:00:00:00",
"mod_time": 1675425919964,
"model": "vmware_vm",
"node_id": 6,
"on_watchlist": true,
"role": "other",
"url": "https://dummy_url/extrahop/#/metrics/devices/00000000000000000000000000000000.0000000000000000/overview/",
"user_mod_time": 0,
"vendor": "VMware",
"vlanid": 0
}
]
}
}
Human Readable Output
Device Details
Display Name IP Address MAC Address Role Vendor URL test 0.0.0.0 00:00:00:00:00:00 other VMware View Device in ExtraHop VM 8 0.0.0.0 00:00:00:00:00:00 other VMware View Device in ExtraHop
extrahop-peers-get
Get all peers for a device from ExtraHop Reveal(x).
Base Command
extrahop-peers-get
Input
| Argument Name | Description | Required |
|---|---|---|
| ip_or_id | The IP address or ExtraHop API ID of the source device to get peer devices. | Required |
| query_from | The beginning timestamp of the time range the query will search, expressed in milliseconds since the epoch. A negative value is evaluated relative to the current time. The default unit for a negative value is milliseconds, but other units can be specified with one of the following unit suffixes: ms, s, m, h, d, w, M, y. See https://docs.extrahop.com/current/rest-api-guide/#supported-time-units- for more details on supported time units and suffixes. Default is -30m. | Optional |
| query_until | The ending timestamp of the time range the query will search, expressed in milliseconds since the epoch. 0 indicates the time of the request. A negative value is evaluated relative to the current time. The default unit for a negative value is milliseconds, but other units can be specified with one of the following unit suffixes: ms, s, m, h, d, w, M, y. See https://docs.extrahop.com/current/rest-api-guide/#supported-time-units- for more details on supported time units and suffixes. | Optional |
| peer_role | The role of the peer device in relation to the origin device. Possible values are: any, client, server. Default is any. | Optional |
| protocol | A filter to only return peers that the source device has communicated with over this protocol. If no value is set, the object includes any protocol. Possible values are: any, AAA, ActiveMQ, AJP, amf, CIFS, DB, DHCP, DICOM, DNS, FIX, FTP, HL7, HTTP, IBMMQ, ICA, IKE/ISAKMP, IMAP, IPFIX, IPsec NAT-T, IRC, iSCSI, Kerberos, L2TP, LDAP, lync-compress, memcache, Modbus, MongoDB, MSMQ, MSN, MSRPC, NetFlow, NFS, NTP, OpenVPN, PCoIP, Perforce, POP3, RDP, Redis, RFB, RTCP, RTP, sFlow, SIP, SMPP, SMTP, SNMP, SSH, SSL, Syslog, TCP, telnet, UDP, WebSocket. Default is any. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ExtraHop.Device.Macaddr | String | The MAC address of the device. |
| ExtraHop.Device.DeviceClass | String | The class of the device. |
| ExtraHop.Device.UserModTime | Number | The time of the most recent update, expressed in milliseconds since the epoch. |
| ExtraHop.Device.AutoRole | String | The role automatically detected by the ExtraHop system. |
| ExtraHop.Device.ParentId | Number | The ID of the parent device. |
| ExtraHop.Device.Vendor | String | The device vendor. |
| ExtraHop.Device.Analysis | string | The level of analysis received by the device. |
| ExtraHop.Device.DiscoveryId | String | The UUID given of the device. |
| ExtraHop.Device.DefaultName | String | The default name for this device. |
| ExtraHop.Device.DisplayName | String | The display name of device. |
| ExtraHop.Device.OnWatchlist | Boolean | Whether the device is on the advanced analysis watch list. |
| ExtraHop.Device.ModTime | Number | The time of the most recent update, expressed in milliseconds since the epoch. |
| ExtraHop.Device.IsL3 | Boolean | Indicates whether the device is a layer 3 device. |
| ExtraHop.Device.Role | String | The role of the device. |
| ExtraHop.Device.DiscoverTime | Number | The time that the device was discovered. |
| ExtraHop.Device.Id | Number | The ID of the device. |
| ExtraHop.Device.Ipaddr4 | String | The IPv4 address for this device. |
| ExtraHop.Device.Vlanid | Number | The unique identifier for the VLAN associated with the device. |
| ExtraHop.Device.Ipaddr6 | string | The IPv6 address of the device. |
| ExtraHop.Device.NodeId | number | The node ID of the sensor associated with the device. |
| ExtraHop.Device.Description | string | A user customizable description of the device. |
| ExtraHop.Device.DnsName | string | The DNS name associated with the device. |
| ExtraHop.Device.DhcpName | string | The DHCP name associated with the device. |
| ExtraHop.Device.CdpName | string | The Cisco Discovery Protocol name associated with the device. |
| ExtraHop.Device.NetbiosName | string | The NetBIOS name associated with the device. |
| ExtraHop.Device.Url | string | Link to the device details page in ExtraHop Reveal(x). |
Command example
!extrahop-peers-get ip_or_id=0.0.0.0 peer_role=server protocol=any query_from=-60m query_until=0
Context Example
{
"ExtraHop": {
"Device": {
"analysis": "advanced",
"analysis_level": 1,
"auto_role": "gateway",
"critical": true,
"default_name": "Cisco Meraki 23D27A",
"device_class": "gateway",
"discover_time": 1655102100000,
"discovery_id": "0000000000000000",
"display_name": "Cisco Meraki 23D27A",
"extrahop_id": "0000000000000000",
"id": 25769805776,
"ipaddr4": "0.0.0.0",
"is_l3": false,
"macaddr": "00:00:00:00:00:00",
"mod_time": 1676638911830,
"node_id": 6,
"on_watchlist": false,
"role": "gateway",
"server_protocols": [
"UDP:NTP"
],
"url": "https://dummy_url/extrahop/#/metrics/devices/overview/",
"user_mod_time": 0,
"vendor": "Cisco Meraki",
"vlanid": 0
}
}
}
Human Readable Output
Device Details
Display Name IP Address MAC Address Role Protocols URL Vendor Cisco Meraki 23D27A 0.0.0.0 00:00:00:00:00:00 gateway Server: UDP:NTP View Device in ExtraHop Cisco Meraki
extrahop-devices-search
Search for devices in ExtraHop Reveal(x).
Base Command
extrahop-devices-search
Input
| Argument Name | Description | Required |
|---|---|---|
| name | The name of the device. This searches for matches on all ExtraHop Reveal(x) name fields (DHCP, DNS, NetBIOS, Cisco Discovery Protocol, etc). | Optional |
| ip | The IP address of the device. | Optional |
| mac | The MAC address of the device. | Optional |
| role | The role of the device. Possible values are: db_server, dhcp_server, dns_server, file_server, firewall, gateway, http_server, domain_controller, web_proxy, load_balancer, pc, medical_device, mobile_device, printer, scanner, custom, voip_phone, other. | Optional |
| software | The OS of the device. Possible values are: android, apple_ios, arista_eos, cisco_ios, cisco_nx-os, chrome_os, linux, mac_os, windows, windows_server, windows_server_2008, windows_server_2008_r2, windows_server_2012, windows_server_2012_r2, windows_server_2016, windows_vista, windows_7, windows_8, windows_8.1, windows_10. | Optional |
| tag | A tag present on the device. | Optional |
| vendor | The vendor of the device, based on MAC address via OUI lookup. Possible values are: alcatel-lucent, apple, arista, asus, brother, canon, cisco, cisco-linksys, citrix, dell, dellemc, d-link, emc, f5, google, hp, htc, huawei, ibm, juniper, kyocera, microsoft, netapp, netgear, nokia, nortel, oracle, paloalto, samsung, 3com, toshiba, virtualbox, vmware, zte. | Optional |
| discover_time | The time that device was first seen by the ExtraHop system, expressed in milliseconds since the epoch. A negative value is evaluated relative to the current time. The default unit for a negative value is milliseconds, but other units can be specified with the following unit suffixes: ms, s, m, h, d, w, M, y. For example, to look one day back enter -1d or -24h. See https://docs.extrahop.com/current/rest-api-guide/#supported-time-units- for more details on supported time units and suffixes. | Optional |
| vlan | The VLAN ID of the Virtual LAN that the device is on. | Optional |
| activity | The activity of the device. Possible values are: aaa_client, aaa_server, ajp_client, ajp_server, amf_client, amf_server, cifs_client, cifs_server, db_client, db_server, dhcp_client, dhcp_server, dicom_client, dicom_server, dns_client, dns_server, fix_client, fix_server, ftp_client, ftp_server, hl7_client, hl7_server, http_client, http_server, ibmmq_client, ibmmq_server, ica_client, ica_server, icmp, iscsi_client, iscsi_server, kerberos_client, kerberos_server, ldap_client, ldap_server, llmnr_client, llmnr_server, memcache_client, memcache_server, modbus_client, modbus_server, mongo_client, mongo_server, msmq, nbns_client, nbns_server, nfs_client, nfs_server, pcoip_client, pcoip_server, pop3_client, pop3_server, rdp_client, rdp_server, redis_client, redis_server, rfb_client, rfb_server, rpc_client, rpc_server, rtcp, rtp, scanner, sip_client, sip_server, smpp_client, smpp_server, smtp_client, smtp_server, ssh_client, ssh_server, ssl_client, ssl_server, tcp, telnet_client, telnet_server, udp, websocket_client, websocket_server, wsman_client, wsman_server. | Optional |
| operator | The compare method applied when matching the fields against their values. For example, to find devices with names that begin with ‘SEA1’ (set name=SEA1, operator=startswith). Possible values are: >, <, <=, >=, =, !=, startswith, exists, not_exists, ~, !~. Default is =. | Optional |
| match_type | The match operator to use when chaining the search fields together. For example, to find all HTTP servers running Windows on the network (set match_type=and, role=http_server, software=windows). Possible values are: and, or, not. Default is and. | Optional |
| active_from | The beginning timestamp for the request. Return only devices active after this time. Time is expressed in milliseconds since the epoch. 0 indicates the time of the request. A negative value is evaluated relative to the current time. The default unit for a negative value is milliseconds, but other units can be specified with one of the following unit suffixes: ms, s, m, h, d, w, M, y. See https://docs.extrahop.com/current/rest-api-guide/#supported-time-units- for more details on supported time units and suffixes. | Optional |
| active_until | The ending timestamp for the request. Return only devices active before this time. Time is expressed in milliseconds since the epoch. 0 indicates the time of the request. A negative value is evaluated relative to the current time. The default unit for a negative value is milliseconds, but other units can be specified with one of the following unit suffixes: ms, s, m, h, d, w, M, y. See https://docs.extrahop.com/current/rest-api-guide/#supported-time-units- for more details on supported time units and suffixes. | Optional |
| limit | The maximum number of devices to return. Default is 10. | Optional |
| l3_only | Only returns layer 3 devices by filtering out any layer 2 parent devices. Possible values are: true, false. Default is true. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ExtraHop.Device.Macaddr | String | The MAC address of the device. |
| ExtraHop.Device.DeviceClass | String | The class of the device. |
| ExtraHop.Device.UserModTime | Number | The time of the most recent update, expressed in milliseconds since the epoch. |
| ExtraHop.Device.AutoRole | String | The role automatically detected by the ExtraHop system. |
| ExtraHop.Device.ParentId | Number | The ID of the parent device. |
| ExtraHop.Device.Vendor | String | The device vendor. |
| ExtraHop.Device.Analysis | string | The level of analysis received by the device. |
| ExtraHop.Device.DiscoveryId | String | The UUID of the device. |
| ExtraHop.Device.DefaultName | String | The default name of the device. |
| ExtraHop.Device.DisplayName | String | The display name of device. |
| ExtraHop.Device.OnWatchlist | Boolean | Whether the device is on the advanced analysis watch list. |
| ExtraHop.Device.ModTime | Number | The time of the most recent update, expressed in milliseconds since the epoch. |
| ExtraHop.Device.IsL3 | Boolean | Indicates whether the device is a layer 3 device. |
| ExtraHop.Device.Role | String | The role of the device. |
| ExtraHop.Device.DiscoverTime | Number | The time that the device was discovered. |
| ExtraHop.Device.Id | Number | The ID of the device. |
| ExtraHop.Device.Ipaddr4 | String | The IPv4 address of the device. |
| ExtraHop.Device.Vlanid | Number | The ID of the VLAN associated with the device. |
| ExtraHop.Device.Ipaddr6 | String | The IPv6 address of the device. |
| ExtraHop.Device.NodeId | Number | The node ID of the sensor associated with the device. |
| ExtraHop.Device.Description | String | A user customizable description of the device. |
| ExtraHop.Device.DnsName | String | The DNS name associated with the device. |
| ExtraHop.Device.DhcpName | String | The DHCP name associated with the device. |
| ExtraHop.Device.CdpName | String | The Cisco Discovery Protocol name associated with the device. |
| ExtraHop.Device.NetbiosName | String | The NetBIOS name associated with the device. |
| ExtraHop.Device.Url | String | Link to the device details page in ExtraHop Reveal(x). |
Command example
!extrahop-devices-search activity=aaa_client discover_time=-10m ip=0.0.0.0 l3_only=true limit=2 mac=00:00:00:00:00:00 match_type=or name=DNS operator=!= role=file_server software=linux tag=tag1 vendor=cisco
Context Example
{
"ExtraHop": {
"Device": [
{
"analysis": "advanced",
"analysis_level": 1,
"auto_role": "other",
"critical": false,
"default_name": "VMware 8",
"device_class": "node",
"discover_time": 1676633640000,
"discovery_id": "0000000000000000",
"display_name": "VMware 8",
"extrahop_id": "0000000000000000",
"id": 25769808421,
"ipaddr4": "0.0.0.0",
"is_l3": false,
"last_seen_time": 1676634840000,
"macaddr": "00:00:00:00:00:00",
"mod_time": 1676634890174,
"model": "vmware_vm",
"node_id": 6,
"on_watchlist": false,
"role": "other",
"url": "https://dummy_url/extrahop/#/metrics/devices/overview/",
"user_mod_time": 0,
"vendor": "VMware",
"vlanid": 0
},
{
"analysis": "advanced",
"analysis_level": 1,
"auto_role": "other",
"critical": false,
"default_name": "VMware 3",
"device_class": "node",
"discover_time": 1676614620000,
"discovery_id": "0000000000000000",
"display_name": "VMware 3",
"extrahop_id": "0000000000000000",
"id": 25769808417,
"ipaddr4": "0.0.0.0",
"is_l3": false,
"last_seen_time": 1676616960000,
"macaddr": "00:00:00:00:00:00",
"mod_time": 1676616977189,
"model": "vmware_vm",
"node_id": 6,
"on_watchlist": false,
"role": "other",
"url": "https://dummy_url/extrahop/#/metrics/devices/overview/",
"user_mod_time": 0,
"vendor": "VMware",
"vlanid": 0
}
]
}
}
Human Readable Output
Device Details
Display Name IP Address MAC Address Role Vendor URL VMware 8 0.0.0.0 00:00:00:00:00:00 other VMware View Device in ExtraHop VMware 3 0.0.0.0 00:00:00:00:00:00 other VMware View Device in ExtraHop
extrahop-protocols-get
Get all active network protocols for a device from ExtraHop Reveal(x).
Base Command
extrahop-protocols-get
Input
| Argument Name | Description | Required |
|---|---|---|
| ip_or_id | The IP address or ExtraHop API ID of the device to get all active network protocols. | Required |
| query_from | The beginning timestamp of the time range the query will search, expressed in milliseconds since the epoch. A negative value is evaluated relative to the current time. The default unit for a negative value is milliseconds, but other units can be specified with one of the following unit suffixes: ms, s, m, h, d, w, M, y. See https://docs.extrahop.com/current/rest-api-guide/#supported-time-units- for more details on supported time units and suffixes. Default is -30m. | Optional |
| query_until | The ending timestamp of the time range the query will search, expressed in milliseconds since the epoch. 0 indicates the time of the request. A negative value is evaluated relative to the current time. The default unit for a negative value is milliseconds, but other units can be specified with one of the following unit suffixes: ms, s, m, h, d, w, M, y. See https://docs.extrahop.com/current/rest-api-guide/#supported-time-units- for more details on supported time units and suffixes. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ExtraHop.Device.Macaddr | String | The MAC address of the device. |
| ExtraHop.Device.DeviceClass | String | The class of the device. |
| ExtraHop.Device.UserModTime | Number | The time of the most recent update, expressed in milliseconds since the epoch. |
| ExtraHop.Device.AutoRole | String | The role automatically detected by the ExtraHop system. |
| ExtraHop.Device.ParentId | Number | The ID of the parent device. |
| ExtraHop.Device.Vendor | String | The device vendor. |
| ExtraHop.Device.Analysis | String | The level of analysis received by the device. |
| ExtraHop.Device.DiscoveryId | String | The UUID of the device. |
| ExtraHop.Device.DefaultName | String | The default name of the device. |
| ExtraHop.Device.DisplayName | String | The display name of device. |
| ExtraHop.Device.OnWatchlist | Boolean | Whether the device is on the advanced analysis watch list. |
| ExtraHop.Device.ModTime | Number | The time of the most recent update, expressed in milliseconds since the epoch. |
| ExtraHop.Device.IsL3 | Boolean | Indicates whether the device is a layer 3 device. |
| ExtraHop.Device.Role | String | The role of the device. |
| ExtraHop.Device.DiscoverTime | Number | The time that the device was discovered. |
| ExtraHop.Device.Id | Number | The ID of the device. |
| ExtraHop.Device.Ipaddr4 | String | The IPv4 address of the device. |
| ExtraHop.Device.Vlanid | Number | The ID of the VLAN associated with the device. |
| ExtraHop.Device.Ipaddr6 | String | The IPv6 address of the device. |
| ExtraHop.Device.NodeId | Number | The node ID of the sensor associated with the device. |
| ExtraHop.Device.Description | String | A user customizable description of the device. |
| ExtraHop.Device.DnsName | String | The DNS name associated with the device. |
| ExtraHop.Device.DhcpName | String | The DHCP name associated with the device. |
| ExtraHop.Device.CdpName | String | The Cisco Discovery Protocol name associated with the device. |
| ExtraHop.Device.NetbiosName | String | The NetBIOS name associated with the device. |
| ExtraHop.Device.Url | String | Link to the device details page in ExtraHop Reveal(x). |
| ExtraHop.Device.ClientProtocols | String | The list of protocols the peer device is communicating on as a client. |
| ExtraHop.Device.ServerProtocols | String | The list of protocols the peer device is communicating on as a server. |
Command example
!extrahop-protocols-get ip_or_id=0.0.0.0 query_from=-20m query_until=0
Context Example
{
"ExtraHop": {
"Device": {
"analysis": "advanced",
"analysis_level": 2,
"auto_role": "other",
"client_protocols": [
"UDP:NTP"
],
"critical": false,
"default_name": "VMware 9",
"device_class": "node",
"dhcp_name": "test",
"discover_time": 1635499650000,
"discovery_id": "0000000000000000",
"display_name": "test",
"extrahop_id": "0000000000000000",
"id": 10000000000,
"ipaddr4": "0.0.0.0",
"is_l3": false,
"macaddr": "00:00:00:00:00:000",
"mod_time": 1676638611398,
"model": "vmware_vm",
"node_id": 6,
"on_watchlist": true,
"role": "other",
"url": "https://dummy_url/extrahop/#/metrics/devices/overview/",
"user_mod_time": 1676290306316,
"vendor": "VMware",
"vlanid": 0
}
}
}
Human Readable Output
Device Activity Found
Display Name IP Address MAC Address Protocols (Client) Role Vendor URL test 0.0.0.0 00:00:00:00:00:000 UDP:NTP other VMware View Device in ExtraHop
extrahop-activity-map-get
Get a link to a live activity map in ExtraHop Reveal(x).
Base Command
extrahop-activity-map-get
Input
| Argument Name | Description | Required |
|---|---|---|
| ip_or_id | The IP address or ExtraHop API ID of the source device to get an activity map. | Required |
| time_interval | The time interval of the live activity map, expressed as the “Last” 30 minutes. For example, specify a value of 30 minutes to get an activity map showing the time range of the last 30 minutes. This field is ignored if from_time and until_time are provided. Possible values are: 30 minutes, 6 hours, 1 day, 1 week. Default is 30 minutes. | Optional |
| from_time | The beginning timestamp of a fixed time range the activity map will display, expressed in seconds since the epoch. | Optional |
| until_time | The ending timestamp of a fixed time range the activity map will display, expressed in seconds since the epoch. | Optional |
| peer_role | The role of the peer devices in relation to the source device. For example, specifying a peer_role of client will show All Clients communicating with the source device. Additionally specifying a protocol of HTTP will result in further filtering and only showing HTTP Clients communicating with the source device. Possible values are: any, client, server. Default is any. | Optional |
| protocol | The protocol over which the source device is communicating. For example, specifying a protocol of HTTP show only HTTP Clients and HTTP Servers communicating with the source device. Additionally specifying a peer_role of client will result in further filtering and only showing HTTP Clients communicating with the source device. Possible values are: any, AAA, ActiveMQ, AJP, amf, CIFS, DB, DHCP, DICOM, DNS, FIX, FTP, HL7, HTTP, IBMMQ, ICA, IKE/ISAKMP, IMAP, IPFIX, IPsec NAT-T, IRC, iSCSI, Kerberos, L2TP, LDAP, lync-compress, memcache, Modbus, MongoDB, MSMQ, MSN, MSRPC, NetFlow, NFS, NTP, OpenVPN, PCoIP, Perforce, POP3, RDP, Redis, RFB, RTCP, RTP, sFlow, SIP, SMPP, SMTP, SNMP, SSH, SSL, Syslog, TCP, telnet, UDP, WebSocket. Default is any. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ExtraHop.ActivityMap.url | String | The link to a visual activity map in ExtraHop Reveal(x). |
Command example
!extrahop-activity-map-get ip_or_id=0.0.0.0 peer_role=server protocol=any time_interval="30 minutes"
Context Example
{
"ExtraHop": {
"ActivityMap": {
"url": "https://dummy_url/extrahop/#/activitymaps?appliance_id=00000000000000000000000000000000&discovery_id=0000000000000000&from=30&interval_type=MIN&object_type=device&protocol=any&role=server&until=0"
}
}
}
Human Readable Output
extrahop-alert-rules-get
Get all alert rules from ExtraHop Reveal(x).
Base Command
extrahop-alert-rules-get
Input
There are no input arguments for this command.
Context Output
| Path | Type | Description |
|---|---|---|
| ExtraHop.Alert.Operator | String | The logical operator applied when comparing the value of the operand field to alert conditions. |
| ExtraHop.Alert.FieldName | String | The name of the monitored metric. |
| ExtraHop.Alert.NotifySnmp | Boolean | Indicates whether to send an SNMP trap when an alert is generated. |
| ExtraHop.Alert.Operand | String | The value to compare against alert conditions. |
| ExtraHop.Alert.IntervalLength | Number | The length of the alert interval, expressed in seconds. |
| ExtraHop.Alert.Author | String | The name of the user that created the alert. |
| ExtraHop.Alert.Name | String | The unique, friendly name for the alert. |
| ExtraHop.Alert.FieldName2 | String | The second monitored metric when applying a ratio. |
| ExtraHop.Alert.RefireInterval | Number | The time interval in which alert conditions are monitored, expressed in seconds. |
| ExtraHop.Alert.ModTime | Number | The time of the most recent update, expressed in milliseconds since the epoch. |
| ExtraHop.Alert.Units | String | The interval in which to evaluate the alert condition. |
| ExtraHop.Alert.ApplyAll | Boolean | Indicates whether the alert is assigned to all available data sources. |
| ExtraHop.Alert.Type | String | The type of alert. |
| ExtraHop.Alert.FieldOp | String | The type of comparison between the “field_name” and “field_name2” fields when applying a ratio. |
| ExtraHop.Alert.Id | Number | The unique identifier for the alert. |
| ExtraHop.Alert.Disabled | Boolean | Indicates whether the alert is disabled. |
| ExtraHop.Alert.Description | String | An optional description for the alert. |
| ExtraHop.Alert.Severity | Number | The severity level of the alert. |
| ExtraHop.Alert.StatName | String | The statistic name for the alert. |
Command example
#### Context Example
```json
{
"ExtraHop": {
"Alert": [
{
"apply_all": false,
"author": "ExtraHop",
"description": "Alert triggered when ratio of DB errors is greater than 1%.",
"disabled": false,
"field_name": "rsp_error",
"field_name2": "rsp",
"field_op": "/",
"id": 15,
"interval_length": 30,
"mod_time": 1617887147538,
"name": "DB Error Ratio - Orange",
"notify_snmp": false,
"operand": "0.01",
"operator": ">",
"refire_interval": 300,
"severity": 3,
"stat_name": "extrahop.application.db",
"type": "threshold",
"units": "none"
},
{
"apply_all": false,
"author": "ExtraHop",
"description": "Alert triggered when ratio of DB errors is greater than 5%.",
"disabled": false,
"field_name": "rsp_error",
"field_name2": "rsp",
"field_op": "/",
"id": 14,
"interval_length": 30,
"mod_time": 1617887147615,
"name": "DB Error Ratio - Red",
"notify_snmp": false,
"operand": "0.05",
"operator": ">",
"refire_interval": 300,
"severity": 1,
"stat_name": "extrahop.application.db",
"type": "threshold",
"units": "none"
},
{
"apply_all": false,
"author": "ExtraHop",
"description": "Alert triggered when ratio of DNS errors is greater than 0.1%.",
"disabled": false,
"field_name": "rsp_error",
"field_name2": "rsp",
"field_op": "/",
"id": 19,
"interval_length": 30,
"mod_time": 1617887147785,
"name": "DNS Error Ratio - Yellow",
"notify_snmp": false,
"operand": "0.001",
"operator": ">",
"refire_interval": 300,
"severity": 5,
"stat_name": "extrahop.application.dns",
"type": "threshold",
"units": "none"
}
]
}
}
Human Readable Output
Found 3 Alert(s)
Apply All Author Description Disabled Field Name Field Name2 Field Op Id Interval Length Mod Time Name Notify Snmp Operand Operator Refire Interval Severity Stat Name Type Units false ExtraHop Alert triggered when ratio of DB errors is greater than 1%. false rsp_error rsp / 15 30 1617887147538 DB Error Ratio - Orange false 0.01 > 300 3 extrahop.application.db threshold none false ExtraHop Alert triggered when ratio of DB errors is greater than 5%. false rsp_error rsp / 14 30 1617887147615 DB Error Ratio - Red false 0.05 > 300 1 extrahop.application.db threshold none false ExtraHop Alert triggered when ratio of DNS errors is greater than 0.1%. false rsp_error rsp / 19 30 1617887147785 DNS Error Ratio - Yellow false 0.001 > 300 5 extrahop.application.dns threshold none
extrahop-packets-search
Search for specific packets in ExtraHop Reveal(x).
Base Command
extrahop-packets-search
Input
| Argument Name | Description | Required |
|---|---|---|
| output | The output format. A pcap file, A keylog.txt file that can be loaded in wireshark to decode ssl packets, or a zip file containing both a packets.pcap and keylog.txt. Possible values are: pcap, keylog_txt, zip. Default is pcap. | Optional |
| limit_bytes | The maximum number of bytes to return. Default is 10MB. | Optional |
| limit_search_duration | The maximum amount of time to run the packet search. The default unit is milliseconds, but other units can be specified with a unit suffix. Default is 5m. | Optional |
| query_from | The beginning timestamp of the time range the search will include, expressed in milliseconds since the epoch. A negative value specifies that the search will begin with packets captured at a time in the past relative to the current time. For example, specify -10m to begin the search with packets captured 10 minutes before the time of the request. The default unit for a negative value is milliseconds, but other units can be specified with one of the following unit suffixes: ms, s, m, h, d, w, M, y. See https://docs.extrahop.com/current/rest-api-guide/#supported-time-units- for more details on supported time units and suffixes. Default is -10m. | Optional |
| query_until | The ending timestamp of the time range the search will include, expressed in milliseconds since the epoch. A 0 value specifies that the search will end with packets captured at the time of the search. A negative value specifies that the search will end with packets captured at a time in the past relative to the current time. For example, specify -5m to end the search with packets captured 5 minutes before the time of the request. The default unit for a negative value is milliseconds, but other units can be specified with one of the following unit suffixes: ms, s, m, h, d, w, M, y. See https://docs.extrahop.com/current/rest-api-guide/#supported-time-units- for more details on supported time units and suffixes. | Optional |
| bpf | The Berkeley Packet Filter (BPF) syntax for the packet search. | Optional |
| ip1 | Returns packets sent to or received by the specified IP address. | Optional |
| port1 | Returns packets sent from or received on the specified port. | Optional |
| ip2 | Returns packets sent to or received by the specified IP address. | Optional |
| port2 | Returns packets sent from or received on the specified port. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| File.Size | Number | The size of the file. |
| File.SHA1 | String | The SHA1 hash of the file. |
| File.SHA256 | String | The SHA256 hash of the file. |
| File.Name | String | The name of the file. |
| File.SSDeep | String | The SSDeep hash of the file. |
| File.EntryID | String | The entry ID of the file. |
| File.Info | String | File information. |
| File.Type | String | The file type. |
| File.MD5 | String | The MD5 hash of the file. |
| File.Extension | String | The file extension. |
Command example
!extrahop-packets-search ip1=0.0.0.0 ip2=0.0.0.0 limit_bytes=10MB limit_search_duration=10m output=pcap port1=8000 port2=8000 query_from=-15m query_until=0
Human Readable Output
Uploaded file: extrahop 2022-12-15 21.12.29 to 21.27.29 IST.pcapDownload.
Property Value Type pcap Size 1,122,020 bytes Info data MD5 710737f2d9874690f130da14da38e7cb SHA1 a89d4696c11ee0a8890d8f4effba8fad891cf05d SHA256 433f238d350d8eb19979f0f513974d97b9e9f3445f99deb75c0a1f46e54de111 SHA512 fbb914a425d324e4d50bdcf15fc31499720e48d9242005c796d91c345dcb44e1f2fb1435d6bf44c89e0f8256dbae43638f5d8175872bcd29e5bf4fbcba4124cb SSDeep 12288:WzC9IOFcF8jgBXx00uMOsOFtKu1R4mF48f6G2GeXCuX:Wgo8cNx3QsODKugmnfjcPX
extrahop-devices-tag
Add or remove a tag from devices in ExtraHop Reveal(x).
Base Command
extrahop-devices-tag
Input
| Argument Name | Description | Required |
|---|---|---|
| tag | The case-sensitive value of the tag. | Required |
| add | The list of IP addresses or ExtraHop API IDs of the devices to tag, comma separated. | Optional |
| remove | The list of IP addresses or ExtraHop API IDs of the devices to remove the tag from, comma separated. | Optional |
Context Output
There is no context output for this command.
Command example
!extrahop-devices-tag tag=MyTag add=0.0.0.0 remove=0.0.0.0
Human Readable Output
Successfully tagged untagged the device/s.
extrahop-alert-rule-create
Create a new alert rule in ExtraHop Reveal(x).
Base Command
extrahop-alert-rule-create
Input
| Argument Name | Description | Required |
|---|---|---|
| apply_all | Indicates whether the alert is assigned to all available data sources. Possible values are: true, false. | Required |
| disabled | Indicates whether the alert is disabled. Possible values are: true, false. | Required |
| field_name | The name of the monitored metric. Only applicable to threshold alerts. | Optional |
| field_name2 | The second monitored metric when applying a ratio. Only applicable to threshold alerts. | Optional |
| field_op | The type of comparison between the field_name and field_name2 fields when applying a ratio. Only applicable to threshold alerts. Possible values are: /, null. | Optional |
| interval_length | The length of the alert interval, expressed in seconds. Only applicable to threshold alerts. Possible values are: 30, 60, 120, 300, 600, 900, 1200, 1800. Default is 30. | Optional |
| name | The unique, friendly name for the alert. | Required |
| notify_snmp | Indicates whether to send an SNMP trap when an alert is generated. Possible values are: true, false. | Required |
| object_type | The type of metric source monitored by the alert configuration. Only applicable to detection alerts. Possible values are: application, device. | Optional |
| operand | The value to compare against alert conditions. The compare method is specified by the value of the operator field. Only applicable to threshold alerts. | Optional |
| operator | The logical operator applied when comparing the value of the operand field to alert conditions. Only applicable to threshold alerts. Possible values are: ==, >, <, >=, <=. | Optional |
| param | The first alert parameter, which is either a key pattern or a data point. Only applicable to threshold alerts. | Optional |
| param2 | The second alert parameter, which is either a key pattern or a data point. Only applicable to threshold alerts. | Optional |
| protocols | The list of monitored protocols. Only applicable to detection alerts. | Optional |
| refire_interval | The time interval in which alert conditions are monitored, expressed in seconds. Possible values are: 300, 600, 900, 1800, 3600, 7200, 14400. | Required |
| severity | The severity level of the alert, which is displayed in the Alert History, email notifications, and SNMP traps. Possible values are: 0, 1, 2, 3, 4, 5, 6, 7. | Required |
| stat_name | The statistic name for the alert. Only applicable to threshold alerts. | Optional |
| type | The type of alert. Possible values are: detection, threshold. | Required |
| units | The interval in which to evaluate the alert condition. Only applicable to threshold alerts. Possible values are: none, period, 1 sec, 1 min, 1 hr. | Optional |
Context Output
There is no context output for this command.
Command example
!extrahop-alert-rule-create apply_all=true interval_length=30 disabled=false name="test10" notify_snmp=false refire_interval=300 severity=4 type=detection object_type=device protocols="udp"
Human Readable Output
Successfully created alert rule.
extrahop-ticket-track
Link an ExtraHop Reveal(x) detection to a Cortex XSOAR incident.
Base Command
extrahop-ticket-track
Input
| Argument Name | Description | Required |
|---|---|---|
| incident_id | The ID of the Cortex XSOAR incident to track. | Required |
| detection_id | The ID of the ExtraHop Reveal(x) detection to track. | Required |
| incident_owner | Owner of the incident. | Optional |
| incident_status | Status of the incident. Possible values are: 0, 1, 2, 3. | Optional |
| incident_close_reason | Reason the incident was closed. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ExtraHop.TicketId | String | Cortex XSOAR incident ID successfully tracked to the ExtraHop Reveal(x) detection. |
Command example
!extrahop-ticket-track detection_id=1234 incident_id=1 incident_owner=John incident_status=1
Context Example
{
"ExtraHop": {
"ExtraHop": {
"TicketId": "1"
}
}
}
Human Readable Output
Successfully linked detection(1234) with incident(1)
extrahop-alert-rule-edit
Modify an alert rule in ExtraHop Reveal(x).
Base Command
extrahop-alert-rule-edit
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_id | The unique identifier for the alert. | Required |
| apply_all | Indicates whether the alert is assigned to all available data sources. Possible values are: true, false. | Required |
| disabled | Indicates whether the alert is disabled. Possible values are: true, false. | Required |
| field_name | The name of the monitored metric. Only applicable to threshold alerts. | Optional |
| field_name2 | The second monitored metric when applying a ratio. Only applicable to threshold alerts. | Optional |
| field_op | The type of comparison between the field_name and field_name2 fields when applying a ratio. Only applicable to threshold alerts. Possible values are: /, null. | Optional |
| interval_length | The length of the alert interval, expressed in seconds. Only applicable to threshold alerts. Possible values are: 30, 60, 120, 300, 600, 900, 1200, 1800. Default is 30. | Optional |
| name | The unique, friendly name for the alert. | Required |
| notify_snmp | Indicates whether to send an SNMP trap when an alert is generated. Possible values are: true, false. | Required |
| object_type | The type of metric source monitored by the alert configuration. Only applicable to detection alerts. Possible values are: application, device. | Optional |
| operand | The value to compare against alert conditions. The compare method is specified by the value of the operator field. Only applicable to threshold alerts. | Optional |
| operator | The logical operator applied when comparing the value of the operand field to alert conditions. Only applicable to threshold alerts. Possible values are: ==, >, <, >=, <=. | Optional |
| param | The first alert parameter, which is either a key pattern or a data point. Only applicable to threshold alerts. | Optional |
| param2 | The second alert parameter, which is either a key pattern or a data point. Only applicable to threshold alerts. | Optional |
| protocols | The list of monitored protocols. Only applicable to detection alerts. | Optional |
| refire_interval | The time interval in which alert conditions are monitored, expressed in seconds. Possible values are: 300, 600, 900, 1800, 3600, 7200, 14400. | Required |
| severity | The severity level of the alert, which is displayed in the Alert History, email notifications, and SNMP traps. Possible values are: 0, 1, 2, 3, 4, 5, 6, 7. | Required |
| stat_name | The statistic name for the alert. Only applicable to threshold alerts. | Optional |
| type | The type of alert. Possible values are: detection, threshold. | Required |
| units | The interval in which to evaluate the alert condition. Only applicable to threshold alerts. Possible values are: none, period, 1 sec, 1 min, 1 hr. | Optional |
Context Output
There is no context output for this command.
Command example
!extrahop-alert-rule-edit interval_length=30 alert_id=36 apply_all=true disabled=false name="t127" notify_snmp=false refire_interval=300 severity=4 type=detection protocols="udp" object_type=device
Human Readable Output
Successfully updated alert rule.
extrahop-watchlist-edit
Add or remove devices from the advanced analysis watchlist in ExtraHop Reveal(x).
Base Command
extrahop-watchlist-edit
Input
| Argument Name | Description | Required |
|---|---|---|
| add | The list of IP addresses or ExtraHop API IDs of the devices to add, comma separated. | Optional |
| remove | The list of IP addresses or ExtraHop API IDs of the devices to remove, comma separated. | Optional |
Context Output
There is no context output for this command.
Command example
!extrahop-watchlist-edit add=0.0.0.0 remove=0.0.0.0
Human Readable Output
Successfully added new devices(0.0.0.0) in the watchlist
Successfully removed devices(0.0.0.0) from the watchlist
extrahop-metrics-list
Get metrics for specified objects from ExtraHop Reveal(x).
Base Command
extrahop-metrics-list
Input
| Argument Name | Description | Required |
|---|---|---|
| cycle | The aggregation period for metrics. Supported values: “auto”, “1sec”, “30sec”, “5min”, “1hr”, “24hr”. Possible values are: auto, 1sec, 30sec, 5min, 1hr, 24hr. |
Required |
| from_time | The beginning timestamp for the request. Return only metrics collected after this time. Time is expressed in milliseconds since the epoch. 0 indicates the time of the request. A negative value is evaluated relative to the current time. The default unit for a negative value is milliseconds, but other units can be specified with a unit suffix. For example, to request devices active in the last 30 minutes, specify the following parameter value: “-30m”. |
Required |
| metric_category | The group of metrics that are searchable in the metric catalog. | Required |
| object_ids | The list of numeric values that represent unique identifiers. Unique identifiers can be retrieved through the /networks, /devices, /applications, /vlans, /devicegroups, /activitygroups, and /appliances resources. For system health metrics, specify the ID of the sensor or console and set the object_type parameter to “system”. | Required |
| object_type | Indicates the object type of unique identifiers specified in the object_ids property. Supported values: “network”, “device”, “application”, “vlan”, “device_group”, “system”. Possible values are: network, device, application, vlan, device_group, system. |
Required |
| until_time | The ending timestamp for the request. Return only metrics collected before this time. Time is expressed in milliseconds since the epoch. 0 indicates the time of the request. A negative value is evaluated relative to the current time. The default unit for a negative value is milliseconds, but other units can be specified with a unit suffix. For example, to request devices active in the last 30 minutes, specify the following parameter value: “-30m”. |
Required |
| metric_specs | An array of metric specification objects. Refer to the ExtraHop REST API Guide at https://docs.extrahop.com/current/rest-api-guide/. |
Required |
Context Output
| Path | Type | Description |
|---|---|---|
| ExtraHop.Metrics.cycle | String | The aggregation period for metrics. |
| ExtraHop.Metrics.node_id | Number | Node ID of the sensor associated with the object. |
| ExtraHop.Metrics.clock | Number | The current time. |
| ExtraHop.Metrics.from | Number | The beginning time from which metrics were collected. |
| ExtraHop.Metrics.until | Number | The ending time that metrics were collected. |
| ExtraHop.Metrics.stats.oid | Number | The ID of the object. |
| ExtraHop.Metrics.stats.time | Number | The time for which metrics were collected. |
| ExtraHop.Metrics.stats.duration | Number | The duration that metrics were collected. |
| ExtraHop.Metrics.stats.values | Unknown | The count value of the metrics that were collected. |
Command example
!extrahop-metrics-list cycle=auto from_time=0 metric_category=http object_ids=0 object_type=application until_time=0 metric_specs="[{\"name\": \"req\", \"key\": \"/GET/\"}]"
Context Example
{
"ExtraHop": {
"Metrics": {
"clock": 1676883600000,
"cycle": "1hr",
"from": 0,
"node_id": 0,
"stats": [
{
"duration": 3600000,
"oid": 0,
"time": 1637740800000,
"values": [
345
]
},
{
"duration": 3600000,
"oid": 0,
"time": 1637744400000,
"values": [
178
]
},
{
"duration": 3600000,
"oid": 0,
"time": 1637751600000,
"values": [
744
]
}
],
"until": 1676883600000
}
}
}
Human Readable Output
Metrics Found:
| Cycle | 30 sec |
|---|---|
| Node Id | 0 |
| Clock | 1676873250000 |
| From Time | 1676871390000 |
| Until Time | 1676871990000 |
| Stats | {‘oid’: 0, ‘time’: 1637740800000, ‘duration’: 30000, ‘values’: [4]}, {‘oid’: 0, ‘time’: 1676871420000, ‘duration’: 30000, ‘values’: [9]}, {‘oid’: 0, ‘time’: 1676871450000, ‘duration’: 30000, ‘values’: [4]}, |
extrahop-detections-list
Get detections from ExtraHop Reveal(x).
Base Command
extrahop-detections-list
Input
| Argument Name | Description | Required |
|---|---|---|
| filter | Detection-specific filters. For eg: { “categories”: [“sec.attack”], “risk_score_min”: 51 } If the categories and category are not specified, then categories will be set to [“sec.attack”]. The category field is deprecated by the API, so please use the categories field instead. Refer to the ExtraHop REST API guide at https://docs.extrahop.com/current/rest-api-guide/. |
Optional |
| from | Returns detections that occurred after the specified date, expressed in milliseconds since the epoch. Detections that started before the specified date are returned if the detection was ongoing at that time. For eg: from=1673508360001. |
Optional |
| limit | Returns no more than the specified number of detections. For eg: limit=10. Default is 200. |
Optional |
| offset | The number of detections to skip for pagination. For eg: offset=100. |
Optional |
| sort | Sorts returned detections by the specified fields. Comma separated “field” “direction” is the accepted format. By default, detections are sorted by most recent update time and then id in ascending order. For eg: sort=”end_time asc,id desc”. |
Optional |
| until | Return detections that ended before the specified date, expressed in milliseconds since the epoch. For eg: until=1673509360001. |
Optional |
| mod_time | Return detections that were modified on or after the specified date, expressed in milliseconds since the epoch. For eg: 1675416916102 . |
Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| ExtraHop.Detections.id | Number | The unique detection ID of the detection. |
| ExtraHop.Detections.start_time | Number | The timestamp when the detection was identified. |
| ExtraHop.Detections.mod_time | Number | The timestamp when the detection was last modified. |
| ExtraHop.Detections.end_time | Number | The timestamp when the detection was completed. |
| ExtraHop.Detections.title | String | The title of the detection. |
| ExtraHop.Detections.description | String | The description of the event for which the detection was created. |
| ExtraHop.Detections.categories | Unknown | The categories associated with the detection. |
| ExtraHop.Detections.risk_score | Number | The risk level of the event. |
| ExtraHop.Detections.type | String | The detection type. |
| ExtraHop.Detections.properties | Unknown | The detection properties. |
| ExtraHop.Detections.participants | Unknown | The participants involved in the event. |
| ExtraHop.Detections.ticket_id | String | The unique ticket ID for the detection that is being tracked. |
| ExtraHop.Detections.assignee | String | The user assigned to the detection. |
| ExtraHop.Detections.status | String | The status of the detection. |
| ExtraHop.Detections.resolution | String | The resolution status of the detection. |
| ExtraHop.Detections.mitre_tactics | Unknown | The MITRE tactics associated with the attack. |
| ExtraHop.Detections.mitre_techniques | Unknown | The MITRE techniques associated with the attack. |
| ExtraHop.Detections.appliance_id | Number | The unique identifier of the sensor on which the attack was detected. |
| ExtraHop.Detections.is_user_created | Boolean | Indicates whether the detection is user-created. |
Command example
!extrahop-detections-list limit=3
Context Example
{
"ExtraHop": {
"Detections": [
{
"appliance_id": 0,
"categories": [
"sec",
"sec.exploit"
],
"description": "The offender was recently observed carrying out a TCP SYN Scan and has now made a successful TCP 3-way handshake to the victim device. Investigate to determine if this is the result of the SYN Scan.",
"end_time": 1676895361452,
"id": 1110161,
"is_user_created": true,
"participants": [
{
"external": false,
"id": 2187135,
"object_type": "ipaddr",
"object_value": "0.0.0.0",
"role": "offender"
},
{
"external": true,
"id": 2187136,
"object_type": "ipaddr",
"object_value": "0.0.0.2",
"role": "victim"
}
],
"risk_score": 50,
"start_time": 1676895361452,
"title": "Test_Detection_1_1676895361452",
"type": "Test_Detection_1_1676895361452",
"mod_time": 1676895361452
},
{
"appliance_id": 0,
"categories": [
"sec",
"sec.exploit"
],
"description": "The offender was recently observed carrying out a TCP SYN Scan and has now made a successful TCP 3-way handshake to the victim device. Investigate to determine if this is the result of the SYN Scan.",
"end_time": 1676895331451,
"id": 1110160,
"is_user_created": true,
"participants": [
{
"external": false,
"id": 2187133,
"object_type": "ipaddr",
"object_value": "0.0.0.0",
"role": "offender"
},
{
"external": true,
"id": 2187134,
"object_type": "ipaddr",
"object_value": "0.0.0.2",
"role": "victim"
}
],
"risk_score": 50,
"start_time": 1676895331451,
"title": "Test_Detection_1_1676895331451",
"type": "Test_Detection_1_1676895331451",
"mod_time": 1676895331451
},
{
"appliance_id": 0,
"categories": [
"sec",
"sec.exploit"
],
"description": "The offender was recently observed carrying out a TCP SYN Scan and has now made a successful TCP 3-way handshake to the victim device. Investigate to determine if this is the result of the SYN Scan.",
"end_time": 1676895301451,
"id": 1110159,
"is_user_created": true,
"participants": [
{
"external": false,
"id": 2187131,
"object_type": "ipaddr",
"object_value": "0.0.0.0",
"role": "offender"
},
{
"external": true,
"id": 2187132,
"object_type": "ipaddr",
"object_value": "0.0.0.2",
"role": "victim"
}
],
"risk_score": 50,
"start_time": 1676895301451,
"title": "Test_Detection_1_1676895301451",
"type": "Test_Detection_1_1676895301451",
"mod_time": 1676895301451
}
]
}
}
Human Readable Output
Found 3 Detection(s)
Detection ID Risk Score Description Categories Start Time 1110161 50 The offender was recently observed carrying out a TCP SYN Scan and has now made a successful TCP 3-way handshake to the victim device. Investigate to determine if this is the result of the SYN Scan. sec,
sec.exploit1676895361452 1110160 50 The offender was recently observed carrying out a TCP SYN Scan and has now made a successful TCP 3-way handshake to the victim device. Investigate to determine if this is the result of the SYN Scan. sec,
sec.exploit1676895331451 1110159 50 The offender was recently observed carrying out a TCP SYN Scan and has now made a successful TCP 3-way handshake to the victim device. Investigate to determine if this is the result of the SYN Scan. sec,
sec.exploit1676895301451
Additional Information
ExtraHop Reveal(x) Playbooks
- ExtraHop - Default
- ExtraHop - CVE-2019-0708 (BlueKeep)
- ExtraHop - Ticket Tracking
- ExtraHop - Get Peers by Host
Use Cases
- Create incidents for every detection that ExtraHop Reveal(x) surfaces in real-time.
- Enable guided investigation and response through playbooks and automation scripts.
- Query the ExtraHop Reveal(x) REST API using the simple and powerful Cortex XSOAR CLI.
Configuration parameters
isFetch— Fetch incidentsincidentType— Incident typeincidentFetchInterval— Incidents Fetch Intervalon_cloud— On Cloudurl— URL (required)apikey— API Keyclient_id— Client IDclient_secret— Client Secretinsecure— Trust any certificate (not secure)proxy— Use system proxy settingsfirst_fetch— First fetch timemax_fetch— How many incidents to fetch each timeadvanced_filter— Advanced Filter
Commands (30)
-
extrahop-activity-map-getGet a link to a live activity map in ExtraHop Reveal(x).
-
extrahop-alert-rule-createCreate a new alert rule in ExtraHop Reveal(x).
-
extrahop-alert-rule-editModify an alert rule in ExtraHop Reveal(x).
-
extrahop-alert-rules-getGet all alert rules from ExtraHop Reveal(x).
-
extrahop-create-alertDeprecatedUse extrahop-alert-rule-create instead. Create a new alert rule in Reveal(x).
-
extrahop-create-alert-ruleDeprecatedUse extrahop-alert-rule-create instead. Create a new alert rule in Reveal(x).
-
extrahop-detections-listGet detections from ExtraHop Reveal(x).
-
extrahop-device-searchDeprecatedUse extrahop-devices-search instead. Search for devices in Reveal(x).
-
extrahop-devices-searchSearch for devices in ExtraHop Reveal(x).
-
extrahop-devices-tagAdd or remove a tag from devices in ExtraHop Reveal(x).
-
extrahop-edit-alertDeprecatedUse extrahop-alert-rule-edit instead. Modify an alert rule in Reveal(x).
-
extrahop-edit-alert-ruleDeprecatedUse extrahop-alert-rule-edit instead. Modify an alert rule in Reveal(x).
-
extrahop-edit-watchlistDeprecatedUse extrahop-watchlist-edit instead. Add or remove devices from the watchlist in Reveal(x).
-
extrahop-get-activity-mapDeprecatedUse extrahop-activity-map-get instead. Get a link to a live activity map in Reveal(x).
-
extrahop-get-alert-rulesDeprecatedUse extrahop-alert-rules-get instead. Get all alert rules from Reveal(x).
-
extrahop-get-alertsDeprecatedUse extrahop-alert-rules-get instead. Get all alert rules from Reveal(x).
-
extrahop-get-peersDeprecatedUse extrahop-peers-get instead. Get all peers for a device from Reveal(x).
-
extrahop-get-protocolsDeprecatedUse extrahop-protocols-get instead. Get all active network protocols for a device from Reveal(x).
-
extrahop-get-watchlistDeprecatedUse extrahop-watchlist-get instead. Get all devices on the watchlist in Reveal(x).
-
extrahop-metrics-listGet metrics for specified objects from ExtraHop Reveal(x).
-
extrahop-packets-searchSearch for specific packets in ExtraHop Reveal(x).
-
extrahop-peers-getGet all peers for a device from ExtraHop Reveal(x).
-
extrahop-protocols-getGet all active network protocols for a device from ExtraHop Reveal(x).
-
extrahop-query-recordsDeprecatedNo available replacement. Query records from Reveal(x).
-
extrahop-search-packetsDeprecatedUse extrahop-packets-search instead. Search for specific packets in Reveal(x).
-
extrahop-tag-devicesDeprecatedUse extrahop-devices-tag instead. Add or remove a tag from devices in Reveal(x).
-
extrahop-ticket-trackLink an ExtraHop Reveal(x) detection to a Cortex XSOAR incident.
-
extrahop-track-ticketDeprecatedUse extrahop-ticket-track instead. Link a Reveal(x) detection to a Demisto Investigation.
-
extrahop-watchlist-editAdd or remove devices from the advanced analysis watchlist in ExtraHop Reveal(x).
-
extrahop-watchlist-getGet all devices on the advanced analysis watchlist in ExtraHop Reveal(x).
import json import traceback from collections import defaultdict from ipaddress import ip_address import demistomock as demisto # noqa: F401 import urllib3 from CommonServerPython import * # noqa: F401 from requests import Response # Disable insecure warnings urllib3.disable_warnings() """ CONSTANTS """ VALID_PEER_ROLES = ["any", "client", "server"] VALID_PROTOCOLS = [ "any", "AAA", "ActiveMQ", "AJP", "amf", "CIFS", "DB", "DHCP", "DICOM", "DNS", "FIX", "FTP", "HL7", "HTTP", "IBMMQ", "ICA", "IKE/ISAKMP", "IMAP", "IPFIX", "IPsec NAT-T", "IRC", "iSCSI", "Kerberos", "L2TP", "LDAP", "lync-compress", "memcache", "Modbus", "MongoDB", "MSMQ", "MSN", "MSRPC", "NetFlow", "NFS", "NTP", "OpenVPN", "PCoIP", "Perforce", "POP3", "RDP", "Redis", "RFB", "RTCP", "RTP", "sFlow", "SIP", "SMPP", "SMTP", "SNMP", "SSH", "SSL", "Syslog", "TCP", "telnet", "UDP", "WebSocket", ] VALID_DEVICE_ROLES = [ "db_server", "dhcp_server", "dns_server", "file_server", "firewall", "gateway", "http_server", "domain_controller", "web_proxy", "load_balancer", "pc", "medical_device", "mobile_device", "printer", "scanner", "custom", "voip_phone", "other", ] VALID_DEVICE_SOFTWARES = [ "android", "apple_ios", "arista_eos", "cisco_ios", "cisco_nx-os", "chrome_os", "linux", "mac_os", "windows", "windows_server", "windows_server_2008", "windows_server_2008_r2", "windows_server_2012", "windows_server_2012_r2", "windows_server_2016", "windows_vista", "windows_7", "windows_8", "windows_8.1", "windows_10", ] VALID_DEVICE_VENDORS = [ "alcatel-lucent", "apple", "arista", "asus", "brother", "canon", "cisco", "cisco-linksys", "citrix", "dell", "dellemc", "d-link", "emc", "f5", "google", "hp", "htc", "huawei", "ibm", "juniper", "kyocera", "microsoft", "netapp", "netgear", "nokia", "nortel", "oracle", "paloalto", "samsung", "3com", "toshiba", "virtualbox", "vmware", "zte", ] VALID_DEVICE_ACTIVITIES = [ "aaa_client", "aaa_server", "ajp_client", "ajp_server", "amf_client", "amf_server", "cifs_client", "cifs_server", "db_client", "db_server", "dhcp_client", "dhcp_server", "dicom_client", "dicom_server", "dns_client", "dns_server", "fix_client", "fix_server", "ftp_client", "ftp_server", "hl7_client", "hl7_server", "http_client", "http_server", "ibmmq_client", "ibmmq_server", "ica_client", "ica_server", "icmp", "iscsi_client", "iscsi_server", "kerberos_client", "kerberos_server", "ldap_client", "ldap_server", "llmnr_client", "llmnr_server", "memcache_client", "memcache_server", "modbus_client", "modbus_server", "mongo_client", "mongo_server", "msmq", "nbns_client", "nbns_server", "nfs_client", "nfs_server", "pcoip_client", "pcoip_server", "pop3_client", "pop3_server", "rdp_client", "rdp_server", "redis_client", "redis_server", "rfb_client", "rfb_server", "rpc_client", "rpc_server", "rtcp", "rtp", "scanner", "sip_client", "sip_server", "smpp_client", "smpp_server", "smtp_client", "smtp_server", "ssh_client", "ssh_server", "ssl_client", "ssl_server", "tcp", "telnet_client", "telnet_server", "udp", "websocket_client", "websocket_server", "wsman_client", "wsman_server", ] VALID_DEVICE_OPERATORS = [">", "<", "<=", ">=", "=", "!=", "startswith", "exists", "not_exists", "~", "!~"] VALID_DEVICE_MATCH_TYPES = ["and", "or", "not"] VALID_TIME_INTERVALS = ["30 minutes", "6 hours", "1 day", "1 week"] VALID_FILE_FORMATS = ["pcap", "keylog_txt", "zip"] VALID_INCIDENT_STATUS = ["0", "1", "2", "3"] TICKET_STATUS_MAP = { "0": "new", # pending "1": "in_progress", # active "2": "closed", # done "3": "acknowledged", # archived } TICKET_SEVERITY = { "0-39": 1, # low "40-69": 2, # medium "70-89": 3, # high "90-100": 4, # critical } VALID_ALERT_RULE_REFIRE_INTERVALS = ["300", "600", "900", "1800", "3600", "7200", "14400"] VALID_ALERT_RULE_TYPE = ["threshold", "detection"] VALID_ALERT_RULE_SEVERITY = ["0", "1", "2", "3", "4", "5", "6", "7"] VALID_ALERT_RULE_INTERVAL_LENGTH = ["30", "60", "120", "300", "600", "900", "1200", "1800"] SORT_DIRECTION = ["asc", "desc"] VALID_ALERT_RULE_OPERATOR = ["==", ">", "<", ">=", "<="] VALID_ALERT_RULE_UNITS = ["none", "period", "1 sec", "1 min", "1 hr"] VALID_ALERT_RULE_OBJECT_TYPES = ["application", "device"] VALID_CYCLES = ["auto", "1sec", "30sec", "5min", "1hr", "24hr"] VALID_OBJECT_TYPES = ["device", "network", "application", "vlan", "device_group", "system"] VALID_METRICS_KEYS = ["cycle", "from", "metric_category", "metric_specs", "object_ids", "object_type", "until"] VALID_DETECTION_KEYS = ["filter", "limit", "offset", "from", "until", "sort", "mod_time"] VALID_FILTER_KEYS = ["assignee", "categories", "category", "resolution", "risk_score_min", "status", "ticket_id", "types"] DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" DEFAULT_FETCH_TYPE = "ExtraHop Detections" FIRST_FETCH = "3 days" MAX_FETCH = 200 EXTRAHOP_MARKDOWN_REGEX = r"(\[[^\]]+\]\(\#\/[^\)]+\))+" XSOAR_VERSION = get_demisto_version_as_str() or "6.5.0" PACK_VERSION = get_pack_version() or "2.3.1" MINIMUM_FIRMWARE_VERSION = "9.3.0" """ CLIENT CLASS """ class ExtraHopClient(BaseClient): def __init__( self, base_url: str, api_key: str, client_id: str, client_secret: str, verify: bool, use_proxy: bool, ok_codes: tuple, on_cloud: bool, ) -> None: """ Prepare constructor for Client class. Calls the constructor of BaseClient class and updates the header with the authentication token. Args: base_url: The url of ExtraHop instance. api_key: The api key to use in header. client_id: The Client ID to use for authentication. client_secret: The Client Secret to use for authentication. verify: True if verify SSL certificate is checked in integration configuration, False otherwise. use_proxy: True if the proxy server needs to be used, False otherwise. """ super().__init__(base_url=base_url, verify=verify, ok_codes=ok_codes, proxy=use_proxy) # Setting up access token in headers. if on_cloud: self._headers: Dict[str, Any] = { "Authorization": f"Bearer {self.get_access_token(client_id=client_id, client_secret=client_secret)}", "ExtraHop-Integration": f"XSOAR-{XSOAR_VERSION}-ExtraHop-{PACK_VERSION}", } else: self._headers = { "Accept": "application/json", "Authorization": f"ExtraHop apikey={api_key}", "ExtraHop-Integration": f"XSOAR-{XSOAR_VERSION}-ExtraHop-{PACK_VERSION}", } def get_access_token(self, client_id: str, client_secret: str) -> str: """Return the token stored in integration context. If the token has expired or is not present in the integration context (in the first case), it calls the Authentication function, which generates a new token and stores it in the integration context. Args: client_id: The Client ID to use for authentication. client_secret: The Client Secret to use for authentication. Returns: str: Authentication token stored in integration context. """ integration_context = get_integration_context() token = integration_context.get("access_token") valid_until = integration_context.get("valid_until") time_now = int(time.time()) # If token exists and is valid, then return it. if (token and valid_until) and (time_now < valid_until): demisto.info("Extrahop token returned from integration context.") return token # Otherwise, generate a new token and store it. token, expires_in = self.authenticate(client_id=client_id, client_secret=client_secret) integration_context = { "access_token": token, "valid_until": time_now + expires_in, # Token expiration time - 30 mins } set_integration_context(integration_context) return token def authenticate(self, client_id: str, client_secret: str) -> tuple[str, int]: """ Get the access token from the ExtraHop API. Args: client_id: The Client ID to use for authentication. client_secret: The Client Secret to use for authentication. Returns: tuple[str,int]: The token and its expiration time in seconds received from the API. """ demisto.info("Generating new authentication token.") req_headers = { "cache-control": "no-cache", "content-type": "application/x-www-form-urlencoded", } req_body = { "grant_type": "client_credentials", } response = self._http_request( method="POST", url_suffix="/oauth2/token", data=req_body, headers=req_headers, auth=(client_id, client_secret) ) token = response.get("access_token") expires_in = response.get("expires_in") return token, expires_in def test_connection(self): """Test the authentication.""" return self._http_request(method="GET", url_suffix="/api/v1/extrahop", error_handler=authentication_error_handler) def get_extrahop_version(self): """Retrieve the ExtraHop version.""" return self._http_request(method="GET", url_suffix="/api/v1/extrahop/version") def get_appliance_uuids(self): """Retrieve the appliance IDs. Returns: Response from the API. """ networks = self._http_request("GET", url_suffix="/api/v1/networks") uuid_lookup = {} for network in networks: uuid_lookup[network["node_id"]] = network.get("appliance_uuid") return uuid_lookup def get_device_by_id(self, device_id: str, ok_codes: tuple = None): """Retrieve the device from the Reveal(X). Args: device_id: Unique ID of the device to be retrieved. ok_codes: The status code to not raise error for while fetching detections. Returns: Response from the API. """ return self._http_request("GET", url_suffix=f"/api/v1/devices/{device_id}", ok_codes=ok_codes) def device_search( self, name: Optional[str], ip: Optional[str], mac: Optional[str], role: Optional[str], software: Optional[str], vendor: Optional[str], tag: Optional[str], discover_time: Optional[str], vlan: Optional[str], activity: Optional[str], operator: Optional[str], match_type: Optional[str], active_from: Optional[str], active_until: Optional[str], limit: Optional[int], l3_only: Optional[bool], ): """Searches for a device from the Reveal(X). Args: name: Name of the device. ip: IP address of the device. mac: Mac address of the device. role: Role of the device. software: Software used in the device. vendor: Vendor of the device. tag: Tags of the device. discover_time: The time device was discovered. vlan: vlan of the device. activity: activity information of the device. operator: Operator of the device. match_type: Match type of the device. active_from: Date the device is active from. active_until: Date the device was last active. limit: Limit of the response l3_only: Whether the device is a layer 3 device. Returns: Response from the API. """ fields = { "name": name, "ipaddr": ip, "macaddr": mac, "role": role, "software": software, "vendor": vendor, "tag": tag, "discover_time": discover_time, "vlan": vlan, "activity": activity, } data: Dict[str, Any] = {} if active_from: data["active_from"] = int(active_from) if active_until: data["active_until"] = int(active_until) if limit: data["limit"] = int(limit) if any(val is not None for val in fields.values()): data["filter"] = {"operator": match_type, "rules": []} rules_list = data["filter"]["rules"] if l3_only: rules_list.append({"field": "ipaddr", "operator": "exists"}) if match_type != "and": data["filter"]["operator"] = "and" rules_list.append({"operator": match_type, "rules": []}) rules_list = data["filter"]["rules"][1]["rules"] for field in fields.items(): if field[1]: search_filter = {"field": field[0], "operator": operator, "operand": field[1]} rules_list.append(search_filter) return self._http_request("POST", url_suffix="/api/v1/devices/search", json_data=data) def get_watchlist(self): """Retrieve all the devices on the watchlist in Reveal(X). Returns: Response from the API. """ return self._http_request("GET", url_suffix="/api/v1/watchlist/devices") def edit_watchlist(self, body: Dict): """Retrieve all devices that are in the watchlist. Args: body: Json payload to pass with the API. """ self._http_request("POST", url_suffix="/api/v1/watchlist/devices", json_data=body, resp_type="response") def get_peers(self, body: Dict): """Retrieve the peers of a device from Reveal(X). Returns: Response from the API. """ return self._http_request("POST", url_suffix="/api/v1/activitymaps/query", json_data=body) def get_alert_rules(self): """Retrieve the alert rules from Reveal(X). Returns: Response from the API. """ return self._http_request("GET", url_suffix="/api/v1/alerts") def patch_detections(self, detection_id: str, body: Dict): """Patch a detection with provided fields. Args: detection_id: The unique identifier for the detection. body: Json payload to pass with the API. Returns: Response from the API. """ return self._http_request("PATCH", url_suffix=f"/api/v1/detections/{detection_id}", json_data=body, resp_type="response") def packets_search( self, output: Optional[str], limit_bytes: Optional[str], limit_search_duration: Optional[str], query_from: Optional[str], query_until: Optional[str], bpf: Optional[str], ip1: Optional[str], port1: Optional[str], ip2: Optional[str], port2: Optional[str], ): """Retrieve the specific packets from Reveal(X). Args: output: The output format limit_bytes: The maximum number of bytes to return. limit_search_duration: The maximum amount of time to run the packet search. query_from: The beginning timestamp of the time range the search will include. query_until: The ending timestamp of the time range the search will include. bpf: The Berkeley Packet Filter (BPF) syntax for the packet search. ip1: Returns packets sent to or received by the specified IP address. port1: Returns packets sent from or received on the specified port. ip2: Returns packets sent to or received by the specified IP address. port2: Returns packets sent from or received on the specified port. Returns: Response from the API. """ body = { "output": output, "limit_bytes": limit_bytes, "limit_search_duration": limit_search_duration, "always_return_body": False, "from": query_from, "until": query_until, "bpf": bpf, "ip1": ip1, "port1": port1, "ip2": ip2, "port2": port2, } return self._http_request("GET", url_suffix="/api/v1/packets/search", json_data=body, resp_type="response") def get_all_tags(self): """Retrieve all available tags from Reveal(X). Returns: Response from the API. """ return self._http_request("GET", url_suffix="/api/v1/tags") def create_new_tag(self, data: Dict): """Create a new tag. Returns: Response from the API. """ return self._http_request("POST", url_suffix="/api/v1/tags", json_data=data, resp_type="response") def tag_untag_devices(self, tag_id: str, data: Dict): """Tag and untag devices for the given tag id. Returns: Response from the API. """ return self._http_request("POST", url_suffix=f"/api/v1/tags/{tag_id}/devices", json_data=data, resp_type="response") def create_alert_rule(self, body: Dict): """Create a new alert rule with specified value. Args: body: Request body of alert rule. Returns: Response from the API. """ return self._http_request( "POST", url_suffix="/api/v1/alerts", json_data=body, empty_valid_codes=[201], return_empty_response=True ) def update_alert_rule(self, alert_id, body): """Update alert rule with specified value. Args: alert_id: The unique identifier of the alert. body: Json payload to pass with the API. Returns: Response from the API. """ return self._http_request("PATCH", url_suffix=f"/api/v1/alerts/{alert_id}", json_data=body, return_empty_response=True) def metrics_list(self, body: Dict): """Retrieves metric information collected about every object from the Reveal(X). Args: body: Request body of metric list. Returns: Response from the API. """ return self._http_request("POST", url_suffix="/api/v1/metrics", json_data=body) def detections_list(self, body): """Retrieve the detections from Reveal(X). Returns: Response from the API. """ return self._http_request("POST", url_suffix="/api/v1/detections/search", json_data=body) def get_detections_by_id(self, detection_id): """Retrieve the detections from Reveal(X). Returns: Response from the API. """ return self._http_request("GET", url_suffix=f"/api/v1/detections/{detection_id}") def get_query_records(self, body: Dict): """Get query records for specified filter. Args: body: JSON payload to pass with the API. """ return self._http_request("POST", url_suffix="/api/v1/records/search", json_data=body) def get_next_page_records(self, cursor): """Get next page records from specified cursor value. Args: cursor: Cursor value to get records. """ body = {"cursor": cursor} params = {"context_ttl": 30000} return self._http_request("POST", url_suffix="/api/v1/records/cursor", json_data=body, params=params) """ EXCEPTION CLASS """ class InvalidValueError(Exception): """Custom exception class for invalid values.""" def __init__(self, arg_name="", arg_value="", arg_list=[], message=""): if not message: message = f"{arg_value} is an invalid value for {arg_name}. Possible values are: {arg_list}" super().__init__(message) """ HELPER FUNCTIONS """ def modify_description(base_url, description): """Modify descriptions of the detections list. Args: base_url: Base URL of the instance. description: Detection description. Returns: Updated description. """ new_link = f"{base_url}/extrahop/#" markdown_data = re.findall(EXTRAHOP_MARKDOWN_REGEX, description) for markdown in markdown_data: # Replacing the '#' to the extrahop platform url if "/extrahop/#" in markdown: new_markdown = markdown.replace("#/extrahop", base_url) else: new_markdown = markdown.replace("#", new_link) description = description.replace(markdown, new_markdown) return description def get_extrahop_server_version(client: ExtraHopClient): """Retrieve and parse the extrahop server version. Args: client: ExtraHop client to be used. Returns: The parsed version of the current extrahop server. """ version = client.get_extrahop_version().get("version") temp = version.split(".") version = ".".join(temp[:3]) return version def remove_empty_elements_from_response(data: Union[Dict, List, None, str]) -> Union[Dict, List, str, None]: """Recursively remove empty lists, empty dicts, or None elements from a dictionary. Args: data: Data from which empty elements are to be removes Returns: Data with no empty fields. """ def empty(x): return x is None or x == {} or x == [] or x == "" if not isinstance(data, dict | list): return data elif isinstance(data, list): return [v for v in (remove_empty_elements_from_response(v) for v in data) if not empty(v)] else: return {k: v for k, v in ((k, remove_empty_elements_from_response(v)) for k, v in data.items()) if not empty(v)} def authentication_error_handler(res: Response) -> None: """Handle 400, 401, 403, 404, 5XX error. Args: res: The response object obtained from API. Raises: ValueError if the status code is 401 or 400. """ if res.status_code == 400: raise ValueError("Error code 400: Attempt to bad request.") elif res.status_code == 401: raise ValueError("Error code 401: Invalid credentials provided.") elif res.status_code == 403: raise ValueError("Error code 403: Attempt to access forbidden resource.") elif res.status_code == 404: raise ValueError("Error code 404: The requested resource cannot be found.") else: raise ValueError("Internal server error.") def trim_spaces_from_args(args: Dict) -> Dict: """Trim spaces from values of the args dict. Args: args: Dict to trim spaces from. Returns: Arguments after trim spaces. """ for key, val in args.items(): if isinstance(val, str): args[key] = val.strip() return args def remove_api_from_base_url(url: str) -> str: """Prepare URL from base URL required for human-readable. Args: url: Base URL of the cloud instance. Returns: ExtraHop cloud instance URL. """ url = url.split(".") url.pop(1) return ".".join(url) def validate_peers_get_arguments(peer_role: Optional[Any], protocol: Optional[Any]) -> None: """Validate arguments for peers-get command. Args: peer_role: The role of the peer device in relation to the origin device. protocol: The protocol over which the source device is communicating. """ if peer_role and peer_role not in VALID_PEER_ROLES: raise InvalidValueError("peer_role", peer_role, VALID_PEER_ROLES) if protocol and protocol not in VALID_PROTOCOLS: raise InvalidValueError("protocol", protocol, VALID_PROTOCOLS) def validate_device_search_arguments( role: Optional[str], software, vendor: Optional[str], activity: Optional[str], operator: Optional[str], match_type: Optional[str], l3_only: Optional[bool], ) -> None: """Validate arguments for peers-get command. Args: role: The role of the device. software: The OS of the device. vendor: The vendor of the device, based on MAC address via OUI lookup. activity: The activity of the device. operator: The compare method applied when matching the fields against their values match_type: The match operator to use when chaining the search fields together. l3_only: Only returns layer 3 devices by filtering out any layer 2 parent devices. """ if role and role not in VALID_DEVICE_ROLES: raise InvalidValueError("role", role, VALID_DEVICE_ROLES) if software and software not in VALID_DEVICE_SOFTWARES: raise InvalidValueError("software", software, VALID_DEVICE_SOFTWARES) if vendor and vendor not in VALID_DEVICE_VENDORS: raise InvalidValueError("vendor", vendor, VALID_DEVICE_VENDORS) if activity and activity not in VALID_DEVICE_ACTIVITIES: raise InvalidValueError("activity", activity, VALID_DEVICE_ACTIVITIES) if operator and operator not in VALID_DEVICE_OPERATORS: raise InvalidValueError("operator", operator, VALID_DEVICE_OPERATORS) if match_type and match_type not in VALID_DEVICE_MATCH_TYPES: raise InvalidValueError("match_type", match_type, VALID_DEVICE_MATCH_TYPES) if l3_only: argToBoolean(l3_only) def validate_activity_map_get_arguments( ip_or_id: Optional[str], time_interval: str, from_time: Optional[str], until_time: Optional[str], peer_role: str, protocol: str, ) -> None: """Validate arguments for peers-get command. Args: ip_or_id: IP address or unique ID of the device. time_interval: Time interval of the live activity map. from_time: The beginning timestamp of a fixed time range. until_time: The ending timestamp of a fixed time range. peer_role: The role of the peer devices in relation to the source device. protocol: The protocol over which the source device is communicating. """ if not ip_or_id.isdigit(): # type: ignore ip_address(ip_or_id) # type: ignore if time_interval and time_interval not in VALID_TIME_INTERVALS: raise InvalidValueError("time_interval", time_interval, VALID_TIME_INTERVALS) if from_time: arg_to_number(from_time, "from_time") if until_time: arg_to_number(until_time, "until_time") if peer_role and peer_role not in VALID_PEER_ROLES: raise InvalidValueError("peer_role", peer_role, VALID_PEER_ROLES) if protocol and protocol not in VALID_PROTOCOLS: raise InvalidValueError("protocol", protocol, VALID_PROTOCOLS) def validate_ticket_track_arguments(incident_status: str): """Validate arguments for ticket-track command. Args: incident_status: The status of the incident. """ if incident_status and incident_status not in VALID_INCIDENT_STATUS: raise InvalidValueError("incident_status", incident_status, VALID_INCIDENT_STATUS) def validate_packets_search_arguments(output: str) -> None: """Validate arguments for peers-get command. Args: output: The output format. """ if output and output not in VALID_FILE_FORMATS: raise InvalidValueError("output", output, VALID_FILE_FORMATS) def validate_add_and_remove_arguments(add: str, remove: str) -> None: """Validate add and remove arguments for command. Args: add: The list of IP or device ID to add tag. remove: The list of IP or device ID to remove tag. """ if not add and not remove: raise DemistoException("No device id provided to add or remove arguments.") def validate_create_or_update_alert_rule_arguments( refire_interval: Optional[str], severity: Optional[str], alert_type: Optional[str] ) -> None: """Validate arguments for extrahop-alert-rule-create and extrahop-alert-rule-update commands. Args: refire_interval: The time interval in which alert conditions are monitored, expressed in seconds. severity: The severity level of the alert, which is displayed in the Alert History, email notifications, and SNMP traps. alert_type: The type of alert. """ if refire_interval and refire_interval not in VALID_ALERT_RULE_REFIRE_INTERVALS: raise InvalidValueError("refire_interval", refire_interval, VALID_ALERT_RULE_REFIRE_INTERVALS) if severity and severity not in VALID_ALERT_RULE_SEVERITY: raise InvalidValueError("severity", severity, VALID_ALERT_RULE_SEVERITY) if alert_type and alert_type not in VALID_ALERT_RULE_TYPE: raise InvalidValueError("type", alert_type, VALID_ALERT_RULE_TYPE) def validate_threshold_alert_rule_arguments( interval_length: Optional[str], operator: Optional[str], units: Optional[str] ) -> None: """Validate arguments for extrahop-alert-rule-create and extrahop-alert-rule-update commands for alert_type: threshold. Args: interval_length: The length of the alert interval, expressed in seconds. operator: The logical operator applied when comparing the value of the operand field to alert conditions units: The interval in which to evaluate the alert condition. """ if interval_length and interval_length not in VALID_ALERT_RULE_INTERVAL_LENGTH: raise InvalidValueError("interval_length", interval_length, VALID_ALERT_RULE_INTERVAL_LENGTH) if operator and operator not in VALID_ALERT_RULE_OPERATOR: raise InvalidValueError("operator", operator, VALID_ALERT_RULE_OPERATOR) if units and units not in VALID_ALERT_RULE_UNITS: raise InvalidValueError("units", units, VALID_ALERT_RULE_UNITS) def validate_metrics_list_arguments(body: Dict) -> None: """Validate arguments for metrics-list command. Args: body: Payload of the API request. """ keys = body.keys() for key in keys: if key not in VALID_METRICS_KEYS: raise InvalidValueError("keys", key, VALID_METRICS_KEYS) if body.get("cycle") not in VALID_CYCLES: raise InvalidValueError("cycle", body["cycle"], VALID_CYCLES) if body.get("object_type") not in VALID_OBJECT_TYPES: raise InvalidValueError("object_type", body["object_type"], VALID_OBJECT_TYPES) def validate_detections_list_arguments(body: Dict) -> None: """Validate arguments for list-detections command. Args: body: The payload of the API request. Raises: DemistoException if invalid input given for an argument. """ body = trim_spaces_from_args(body) for key in body: if key not in VALID_DETECTION_KEYS: raise InvalidValueError("key", key, VALID_DETECTION_KEYS) if body.get("filter"): for key in body["filter"]: if key not in VALID_FILTER_KEYS: raise InvalidValueError("key", key, VALID_FILTER_KEYS) if body.get("from") and body.get("until") and body["from"] > body["until"]: raise DemistoException('Input for "from" should always be less than that of "until".') if isinstance(body.get("limit"), int): if body["limit"] <= 0: raise DemistoException("Invalid input for field limit. It should have numeric value greater than zero.") body["limit"] = min(body["limit"], 200) else: body["limit"] = 200 if isinstance(body.get("offset"), int): if body["offset"] < 0: raise DemistoException("Invalid input for field offset. It should have numeric value greater than or equal to zero.") else: body["offset"] = 0 def add_default_category_for_filter_of_detection_list(_filter: Dict) -> None: """Set a default category for filter argument. Args: _filter: Filter argument for detection list command. """ if "category" not in _filter: if "categories" not in _filter: _filter["categories"] = ["sec.attack"] elif isinstance(_filter.get("categories"), list): valid_categories = [] for category in _filter.get("categories", []): if isinstance(category, str): category = category.strip() if category: valid_categories.append(category) _filter["categories"] = valid_categories if valid_categories else ["sec.attack"] def format_protocol_stack(protocol_list: List) -> str: """Formats the protocol stack. Args: protocol_list: List of protocols. Returns: String of formatted protocols. """ if len(protocol_list) > 1: protos = protocol_list[1:] else: protos = protocol_list return ":".join(protos) def sort_protocols(protos_by_weight: Dict) -> List: """Sort protocols by weight. Args: protos_by_weight: Weighted dictionary of protocols. Returns: Sorted List fo protocols. """ sorted_protos = sorted(protos_by_weight.items(), key=lambda x: x[1], reverse=True) return [proto_tuple[0] for proto_tuple in sorted_protos] def get_device_by_ip(client: ExtraHopClient, ip, active_from: str = None, active_until: str = None, limit: int = None): """Retrieve the device by IP address. Args: client: ExtraHop client to be used. ip: IP address of the device. active_from: Time the device was active from. active_until: Time the device was last active. limit: Number of devices to retrieve. Returns: Devices. """ devices = client.device_search( name=None, ip=ip, mac=None, role=None, software=None, vendor=None, tag=None, discover_time=None, vlan=None, activity=None, operator="=", match_type="and", active_from=active_from, active_until=active_until, limit=limit, l3_only=True, ) if devices: return devices[0] else: raise DemistoException(f"Error the IP Address {ip} was not found in ExtraHop.") # noqa: E713 def get_devices_by_ip_or_id( client: ExtraHopClient, devices_str, active_from: str = None, active_until: str = None, limit: int = None, id_only: bool = False, ) -> List: """Retrieve the devices by IP address or ID. Args: client: Extrahop client to be used. devices_str: String os devices. active_from: Time the device was active from. active_until: Time the device was last active. limit: Number of devices to retrieve. id_only: Whether to retrieve devices by id only. Returns: List of devices. """ devices = [] for item in str(devices_str).split(","): if item.isdigit(): if id_only: devices.append(int(item)) else: device = client.get_device_by_id(item) devices.append(device) else: try: ip_address(item) except ValueError: raise DemistoException(f"Error parsing IP Address {item}") device = get_device_by_ip(client, item, active_from, active_until, limit) if id_only: devices.append(int(device["id"])) else: devices.append(device) return devices def get_protocols(client: ExtraHopClient, ip_or_id, query_from, query_until) -> Dict: """Retrieve all the protocols for a device from the Reveal(X) in the given time range. Args: client: ExtraHop client to be used. ip_or_id: IP or ID of the object to get protocols for. query_from: Time since epoch to fetch the protocols. query_until: Time until epoch to fetch the protocols. Returns: Dictionory of client and server protocols. """ device = get_devices_by_ip_or_id(client, ip_or_id)[0] api_id = int(device["id"]) if device.get("analysis") == "discovery": demisto.results( { "Type": entryTypes["note"], "ContentsFormat": formats["markdown"], "Contents": ( f"This Device is in Discovery Mode. Configure your [Analysis Priorities]" f"(https://docs.extrahop.com/current/analysis_priorities/) or add this device to the " # noqa: E231 f"[Watchlist](https://docs.extrahop.com/current/analysis-priorities-faq/" # noqa: E231 f"#what-is-the-watchlist) manually with: `!extrahop-edit-watchlist add={api_id}`" ), } ) body = { "edge_annotations": ["protocols"], "from": query_from, "walks": [{"origins": [{"object_id": api_id, "object_type": "device"}], "steps": [{}]}], } if query_until: body["until"] = query_until activitymap = client.get_peers(body) client_protocols: defaultdict[str, int] = defaultdict(int) server_protocols: defaultdict[str, int] = defaultdict(int) for edge in activitymap["edges"]: if "annotations" in edge and "protocols" in edge.get("annotations"): for protocol_list in edge.get("annotations", {}).get("protocols"): proto_stack = format_protocol_stack(protocol_list["protocol"]) if edge.get("from") == api_id: client_protocols[proto_stack] += protocol_list["weight"] elif edge.get("to") == api_id: server_protocols[proto_stack] += protocol_list["weight"] device["client_protocols"] = sort_protocols(client_protocols) device["server_protocols"] = sort_protocols(server_protocols) return device def peers_get( client: ExtraHopClient, ip_or_id: Optional[Any], query_from: Optional[Any], query_until: Optional[Any], peer_role: Optional[Any], protocol: Optional[Any], ) -> List: """Retrieve peers of a device from Reveal(X). Args: client: ExtraHop client to be used. ip_or_id: IP address or ID of the device. query_from: Beginning timestamp of the range. query_until: Ending timestamp of the range. peer_role: The role of the peer device in relation to the origin device. protocol: Communication protocol. Returns: List of peers of the device. """ device = get_devices_by_ip_or_id(client, ip_or_id)[0] api_id = int(device["id"]) if device["analysis"] == "discovery": demisto.results( { "Type": entryTypes["note"], "ContentsFormat": formats["markdown"], "Contents": ( f"This Device is in Discovery Mode. Configure your [Analysis Priorities]" f"(https://docs.extrahop.com/current/analysis_priorities/) or add this device to the " # noqa: E231 f"[Watchlist](https://docs.extrahop.com/current/analysis-priorities-faq/" # noqa: E231 f"#what-is-the-watchlist) manually with: `!extrahop-edit-watchlist add={api_id}`" ), } ) body = { "edge_annotations": ["protocols"], "from": query_from, "walks": [ { "origins": [{"object_id": api_id, "object_type": "device"}], "steps": [{"relationships": [{"protocol": protocol, "role": peer_role}]}], } ], } if query_until: body["until"] = query_until activitymap = client.get_peers(body) peers: defaultdict[str, dict] = defaultdict( lambda: {"weight": 0, "client_protocols": defaultdict(int), "server_protocols": defaultdict(int)} ) for edge in activitymap["edges"]: if edge["to"] == api_id: peer_id = edge["from"] role_key = "client_protocols" else: peer_id = edge["to"] role_key = "server_protocols" peers[peer_id]["weight"] += edge["weight"] # add protocols if "annotations" in edge and "protocols" in edge["annotations"]: for protocol_list in edge["annotations"]["protocols"]: proto_stack = format_protocol_stack(protocol_list["protocol"]) peers[peer_id][role_key][proto_stack] += protocol_list["weight"] peer_devices = [] peer_ids_by_weight = [peer[0] for peer in sorted(peers.items(), key=lambda x: x[1]["weight"], reverse=True)] # Lookup each peer device by id for peer_id in peer_ids_by_weight: device = client.get_device_by_id(peer_id) if peer_role in ("any", "client"): device["client_protocols"] = sort_protocols(peers[peer_id]["client_protocols"]) if peer_role in ("any", "server"): device["server_protocols"] = sort_protocols(peers[peer_id]["server_protocols"]) peer_devices.append(device) return peer_devices def get_activity_map( client: ExtraHopClient, ip_or_id: Optional[str], time_interval: Optional[Any], from_time: Optional[Any], until_time: Optional[Any], peer_role: str, protocol: str, server_url: str, ): """Retrieve the activity map link for a device from Reveal(X). Args: client: ExtraHop client to be used. ip_or_id: The IP Address or ExtraHop API ID of the source device to get an activity map. time_interval: The time interval of the live activity map. from_time: The beginning timestamp of a fixed time range. until_time: The ending timestamp of a fixed time range. peer_role: The role of the peer devices in relation to the source device. protocol: The protocol over which the source device is communicating. server_url: Server URL. Returns: Activity map link. """ device = get_devices_by_ip_or_id(client, ip_or_id)[0] time_intervals = {"30 minutes": (30, "MIN"), "6 hours": (6, "HR"), "1 day": (1, "DAY"), "1 week": (1, "WK")} if from_time or until_time: if from_time and until_time: interval = "DT" start = from_time end = until_time else: raise ValueError("When using a fixed time range both from_time and until_time timestamps need to be provided.") else: start, interval = time_intervals.get(time_interval, (30, "MIN")) # type: ignore end = 0 activity_map_params = { "server": server_url, "app_id": client.get_appliance_uuids()[device.get("node_id")], "disc_id": device.get("discovery_id"), "start": start, "interval": interval, "obj": "device", "proto": protocol, "role": peer_role, "end": end, } activity_map_link_format = ( "{server}/extrahop/#/activitymaps" "?appliance_id={app_id}" "&discovery_id={disc_id}" "&from={start}" "&interval_type={interval}" "&object_type={obj}" "&protocol={proto}" "&role={role}" "&until={end}" ) return activity_map_link_format.format(**activity_map_params) def prepare_devices_get_output(response: List, appliance_uuids: List, server_url: str) -> str: """Prepare human-readable output for watchlist-get command. Args: appliance_uuids: UUID of the appliance. response: Response from the API. server_url: Server URL. Returns: markdown string to be displayed in the war room. """ hr_outputs = [] headers = ["Display Name", "IP Address", "MAC Address", "Role", "Vendor", "URL"] for device in response: hr = { "Display Name": device.get("display_name"), "IP Address": device.get("ipaddr4", device.get("ipaddr6")), "MAC Address": device.get("macaddr"), "Role": device.get("role"), "Vendor": device.get("vendor"), } device_url = ( f'{server_url}/extrahop/#/metrics/devices/{appliance_uuids[device.get("node_id")]}.' f'{device.get("discovery_id")}/overview/' ) hr["URL"] = f"[View Device in ExtraHop]({device_url})" device["url"] = device_url if "client_protocols" in device or "server_protocols" in device: hr["Protocols"] = {} # re-arrange headers to add protocol information headers = ["Display Name", "IP Address", "MAC Address", "Role", "Protocols", "URL", "Vendor"] if "client_protocols" in device: hr["Protocols"]["Client"] = ", ".join(device.get("client_protocols", [])) if "server_protocols" in device: hr["Protocols"]["Server"] = ", ".join(device.get("server_protocols", [])) hr_outputs.append(hr) if not len(response): return "No Devices found" return tableToMarkdown("Device Details:\n", hr_outputs, headers=headers, removeNull=True) def prepare_protocol_get_output(device, appliance_uuids, server_url) -> str: """Prepare human-readable output for protocol-get command. Args: device: The device data received from API. appliance_uuids: UUID of the appliance. server_url: Server URL. Returns: Markdown string to be displayed in the war room. """ if not device.get("client_protocols") and not device.get("server_protocols"): return "No Protocol activity found" device_url = ( f'{server_url}/extrahop/#/metrics/devices/{appliance_uuids[device.get("node_id")]}.' f'{device.get("discovery_id")}/overview/' ) hr_outputs = { "Display Name": device.get("display_name"), "IP Address": device.get("ipaddr4", device.get("ipaddr6")), "MAC Address": device.get("macaddr"), "Protocols (Client)": ", ".join(device.get("client_protocols", [])), "Protocols (Server)": ", ".join(device.get("server_protocols", [])), "Role": device.get("role"), "Vendor": device.get("vendor"), "URL": f"[View Device in ExtraHop]({device_url})", } device["url"] = device_url headers = ["Display Name", "IP Address", "MAC Address", "Protocols (Client)", "Protocols (Server)", "Role", "Vendor", "URL"] return tableToMarkdown("Device Activity Found:\n", hr_outputs, headers=headers, removeNull=True) def prepare_activity_map_link_output(activity_map_link: str) -> str: """Prepare human-readable output for activity-map-get command. Args: activity_map_link: Live link of an activity map. Returns: markdown string to be displayed in the war room. """ return f"[View Live Activity Map in ExtraHop]({activity_map_link})" def prepare_alert_rules_get_output(alerts: Dict) -> str: """Prepare human-readable output for get-alert-rules command. Args: alerts: List of alert response from the API. Returns: markdown string to be displayed in the war room. """ if len(alerts) == 0: return "No Alerts were found." return tableToMarkdown(f"Found {len(alerts)} Alert(s)", alerts, headerTransform=string_to_table_header, removeNull=True) def prepare_metrics_list_output(response: Dict) -> str: """Prepare human-readable output for metrics-list command. Args: response: List of metrics response from the API. Returns: markdown string to be displayed in the war room. """ hr_output = { "Cycle": response.get("cycle"), "Node ID": response.get("node_id"), "Clock": response.get("clock"), "From Time": response.get("from"), "Until Time": response.get("until"), "Stats": response.get("stats"), } headers = ["Cycle", "Node ID", "Clock", "From Time", "Until Time", "Stats"] return tableToMarkdown("Metrics Found:\n", hr_output, headers=headers, removeNull=True) def parse_location_header(location: str) -> str: """Retrieve the tag_id from the location field in header. Args: location: The location field in the header of the response received. Returns: The tag ID of the new created tag. """ if location: last_slash_index = location.rindex("/") + 1 tag_id = location[last_slash_index:] if tag_id.isdigit(): return tag_id # return error in any other case raise DemistoException("Error unable to parse ExtraHop API response location header.") def devices_tag(client: ExtraHopClient, tag: str, add: str, remove: str) -> None: """Tag or untag devices with the given tag name. Args: client: ExtraHop client to be used. tag: The tag which is added or removed from the devices. add: The list of IP or device ID to add tag. remove: The list of IP or device ID to remove tag. """ body = {} if add: body["assign"] = get_devices_by_ip_or_id(client, add, id_only=True) if remove: body["unassign"] = get_devices_by_ip_or_id(client, remove, id_only=True) all_tags = client.get_all_tags() for current_tag in all_tags: if current_tag["name"] == tag: tag_id = current_tag["id"] break else: if remove and not add: raise DemistoException(f"The tag {tag} does not exist, nothing to remove.") tag_create_res = client.create_new_tag(data={"name": tag}) tag_location = tag_create_res.headers.get("location") tag_id = parse_location_header(tag_location) client.tag_untag_devices(tag_id, data=body) def prepare_list_detections_output(detections) -> str: """Prepare human-readable output for list-detections command. Args: detections: List of detection response from the API. Returns: markdown string to be displayed in the war room. """ hr_outputs = [] headers = ["Detection ID", "Risk Score", "Description", "Categories", "Status", "Resolution", "Start Time"] for detection in detections: hr_output = { "Detection ID": detection.get("id"), "Risk Score": detection.get("risk_score"), "Description": detection.get("description"), "Categories": detection.get("categories"), "Status": detection.get("status"), "Resolution": detection.get("resolution"), "Start Time": detection.get("start_time"), } hr_outputs.append(hr_output) return tableToMarkdown(f"Found {len(hr_outputs)} Detection(s)", hr_outputs, headers=headers, removeNull=True) def is_version_greater_or_equal(version1, version2): """ Returns True if version1 >= version2, else False. Args: version1: The first version to compare. version2: The second version to compare. Returns: True if version1 >= version2, else False """ v1_parts = list(map(int, version1.split("."))) v2_parts = list(map(int, version2.split("."))) length_diff = len(v1_parts) - len(v2_parts) if length_diff > 0: v2_parts.extend([0] * length_diff) elif length_diff < 0: v1_parts.extend([0] * abs(length_diff)) for a, b in zip(v1_parts, v2_parts): if a > b: return True elif a < b: return False return True """ COMMAND FUNCTIONS """ def validate_fetch_incidents_params(params: dict, last_run: dict) -> Dict: """ Validate the parameter list for fetch incidents. Args: params: Dictionary containing demisto configuration parameters last_run: last run returned by function demisto.getLastRun Returns: Dictionary containing validated configuration parameters in proper format. """ first_fetch = arg_to_datetime(params.get("first_fetch", FIRST_FETCH)) detection_start_time = int(first_fetch.timestamp() * 1000) # type: ignore if last_run and "detection_start_time" in last_run: detection_start_time = last_run.get("detection_start_time") # type: ignore offset = 0 if last_run and "offset" in last_run: offset = last_run.get("offset") # type: ignore return {"detection_start_time": detection_start_time, "offset": offset} def append_participant_device_data(client: ExtraHopClient, detections: CommandResults) -> CommandResults: """Append the device data of the participants present in the detection. Args: client: ExtraHop client to be used. detections: The command result object of detection data fetched from ExtraHop. Returns: CommandResult object with device data of the participants. """ for detection in detections.outputs: # type: ignore detection["device_data"] = [] for participant in detection.get("participants", []): if participant.get("object_type") == "device": if not participant.get("object_id"): continue object_id = participant.get("object_id") device_data = client.get_device_by_id(object_id, (404, 200, 204, 201)) else: if not participant.get("object_value"): continue ip = participant.get("object_value") device_data = client.device_search( name=None, ip=ip, mac=None, role=None, software=None, vendor=None, tag=None, discover_time=None, vlan=None, activity=None, operator="=", match_type="and", active_from=None, active_until=None, limit=None, l3_only=True, ) if device_data: detection["device_data"].append(device_data) return detections def fetch_extrahop_detections(client: ExtraHopClient, advanced_filter: Dict, last_run: Dict, on_cloud: bool) -> tuple[List, Dict]: """Fetch detections from ExtraHop according to the given filter. Args: client:ExtraHop client to be used. advanced_filter: The advanced_filter given by the user to filter out the required detections. last_run: Last run returned by function demisto.getLastRun on_cloud: Indicator for the instance hosted on cloud. Returns: List of incidents to be pushed into XSOAR. """ try: already_fetched: List[str] = last_run.get("already_fetched", []) incidents: List[Dict] = [] detection_start_time = advanced_filter["mod_time"] detections = detections_list_command(client, {}, on_cloud=on_cloud, advanced_filter=advanced_filter) if detections.outputs: detections = append_participant_device_data(client, detections) for detection in detections.outputs: # type: ignore detection_id = detection.get("id") if detection_id not in already_fetched: detection.update(get_mirroring()) incident = { "name": str(detection.get("type", "")), "occurred": datetime.utcfromtimestamp(detection["start_time"] / 1000).strftime(DATE_FORMAT), "severity": next( ( severity for range_str, severity in TICKET_SEVERITY.items() if detection.get("risk_score") in range(*map(int, range_str.split("-"))) ), None, ), "rawJSON": json.dumps(detection), } incidents.append(incident) already_fetched.append(detection_id) else: demisto.info(f"Extrahop already fetched detection with id: {detection_id}") if len(incidents) < advanced_filter["limit"]: offset = 0 detection_start_time = detections.outputs[-1]["mod_time"] + 1 if incidents else detection_start_time # type: ignore else: offset = advanced_filter["offset"] + len(incidents) except Exception as error: raise DemistoException(f"extrahop: exception occurred {error!s}") demisto.info(f"Extrahop fetched {len(incidents)} incidents where the advanced filter is {advanced_filter}") last_run["detection_start_time"] = int(detection_start_time) last_run["offset"] = offset last_run["already_fetched"] = already_fetched return incidents, last_run def fetch_incidents(client: ExtraHopClient, params: Dict, last_run: Dict, on_cloud: bool): """Fetch the specified ExtraHop entity and push into XSOAR. Args: client: ExtraHop client to be used. params: Integration configuration parameters. last_run: The last_run dictionary having the state of previous cycle. on_cloud: Indicator for the instance hosted on cloud. """ demisto.info( f"Extrahop fetch_incidents invoked with advanced_filter: {params.get('advanced_filter', '')}, " f"first_fetch: {params.get('first_fetch', '')} and last_run: {last_run}" ) fetch_params = validate_fetch_incidents_params(params, last_run) now = datetime.now() next_day = now + timedelta(days=1) if last_run.get("version_recheck_time", 1581852287000) < int(now.timestamp() * 1000): version = get_extrahop_server_version(client) last_run["version_recheck_time"] = int(next_day.timestamp() * 1000) if not is_version_greater_or_equal(version, MINIMUM_FIRMWARE_VERSION): raise DemistoException( "This integration works with ExtraHop firmware version greater than or equal to " f"{MINIMUM_FIRMWARE_VERSION} and current firmware version being used by you is {version}" ) advanced_filter = params.get("advanced_filter") if advanced_filter and advanced_filter.strip(): try: _filter = json.loads(advanced_filter) add_default_category_for_filter_of_detection_list(_filter) except json.JSONDecodeError as error: raise ValueError("Invalid JSON string provided for advanced filter.") from error else: _filter = {"categories": ["sec.attack"]} advanced_filter = { "filter": _filter, "mod_time": fetch_params["detection_start_time"], "until": 0, "limit": MAX_FETCH, "offset": fetch_params["offset"], "sort": [{"direction": "asc", "field": "mod_time"}], } incidents, next_run = fetch_extrahop_detections(client, advanced_filter, last_run, on_cloud) demisto.info(f"Extrahop next_run is {next_run}") return incidents, next_run def watchlist_get_command(client: ExtraHopClient, on_cloud: bool) -> CommandResults: """Retrieve all the devices on the watchlist in Reveal(X). Args: client: ExtraHop client to be used. on_cloud: Check if ExtraHop instance is on cloud. Returns: CommandResult object """ response = client.get_watchlist() appliance_uuids = client.get_appliance_uuids() server_url = client._base_url if on_cloud: server_url = remove_api_from_base_url(server_url) readable_output = prepare_devices_get_output(response, appliance_uuids, server_url) return CommandResults( outputs_prefix="ExtraHop.Device", outputs_key_field="id", outputs=remove_empty_elements_from_response(response), readable_output=readable_output, raw_response=response, ) def peers_get_command(client: ExtraHopClient, args: Dict[str, Any], on_cloud: bool) -> CommandResults: """Retrieve all the peers for a device from the Reveal(X). Args: client: ExtraHop client to be used. args: arguments obtained from demisto.args(). on_cloud: Check if ExtraHop instance is on cloud. Returns: CommandResult object """ ip_or_id = args.get("ip_or_id") query_from = args.get("query_from", "-30m") query_until = args.get("query_until") peer_role = args.get("peer_role") protocol = args.get("protocol") validate_peers_get_arguments(peer_role, protocol) peer_devices = peers_get(client, ip_or_id, query_from, query_until, peer_role, protocol) appliance_uuids = client.get_appliance_uuids() server_url = client._base_url if on_cloud: server_url = remove_api_from_base_url(server_url) readable_output = prepare_devices_get_output(peer_devices, appliance_uuids, server_url) return CommandResults( outputs_prefix="ExtraHop.Device", outputs_key_field="id", outputs=remove_empty_elements_from_response(peer_devices), readable_output=readable_output, raw_response=peer_devices, ) def devices_search_command(client: ExtraHopClient, args: Dict[str, Any], on_cloud: bool) -> CommandResults: """Retrieve the devices from Reveal(X). Args: client: ExtraHop client to be used: args: Arguments obtained from demisto.args(). on_cloud: Check if ExtraHop instance is on cloud. Returns: CommandResult object """ name = args.get("name") ip = args.get("ip") mac = args.get("mac") role = args.get("role") software = args.get("software") vendor = args.get("vendor") tag = args.get("tag") discover_time = args.get("discover_time") vlan = args.get("vlan") activity = args.get("activity") operator = args.get("operator") match_type = args.get("match_type") active_from = args.get("active_from") active_until = args.get("active_until") limit = args.get("limit", 10) l3_only = args.get("l3_only", True) validate_device_search_arguments(role, software, vendor, activity, operator, match_type, l3_only) devices = client.device_search( name, ip, mac, role, software, vendor, tag, discover_time, vlan, activity, operator, match_type, active_from, active_until, limit, l3_only, ) appliance_uuids = client.get_appliance_uuids() server_url = client._base_url if on_cloud: server_url = remove_api_from_base_url(server_url) readable_output = prepare_devices_get_output(devices, appliance_uuids, server_url) return CommandResults( outputs_prefix="ExtraHop.Device", outputs_key_field="id", outputs=remove_empty_elements_from_response(devices), readable_output=readable_output, raw_response=devices, ) def protocols_get_command(client: ExtraHopClient, args: Dict[str, Any], on_cloud: bool) -> CommandResults: """Retrieve all active network protocols for a device from Reveal(x). Args: client: ExtraHop client to be used. args: Arguments obtained from demisto.args() on_cloud: Check if ExtraHop instance is on cloud. Returns: CommandResult object """ ip_or_id = args.get("ip_or_id") query_from = args.get("query_from") query_until = args.get("query_until") device = get_protocols(client, ip_or_id, query_from, query_until) server_url = client._base_url if on_cloud: server_url = remove_api_from_base_url(server_url) appliance_uuids = client.get_appliance_uuids() readable_output = prepare_protocol_get_output(device, appliance_uuids, server_url) return CommandResults( outputs_prefix="ExtraHop.Device", outputs_key_field="id", outputs=remove_empty_elements_from_response(device), readable_output=readable_output, raw_response=device, ) def activity_map_get_command(client: ExtraHopClient, args: Dict[str, Any], on_cloud: bool) -> CommandResults: """Retrieve the activity of a device from Reveal(x). Args: client: ExtraHop client to be used. args: Arguments obtained from demisto.args() on_cloud: Check if ExtraHop instance is on cloud. Returns: CommandResult object """ ip_or_id = args.get("ip_or_id") time_interval = args.get("time_interval", "30 minutes") from_time = args.get("from_time") until_time = args.get("until_time") peer_role = args.get("peer_role", "any") protocol = args.get("protocol", "any") validate_activity_map_get_arguments(ip_or_id, time_interval, from_time, until_time, peer_role, protocol) server_url = client._base_url if on_cloud: server_url = remove_api_from_base_url(server_url) activity_map_link = get_activity_map(client, ip_or_id, time_interval, from_time, until_time, peer_role, protocol, server_url) context = {"url": activity_map_link} readable_output = prepare_activity_map_link_output(activity_map_link) return CommandResults( outputs_prefix="ExtraHop.ActivityMap", outputs_key_field="url", outputs=remove_empty_elements_from_response(context), readable_output=readable_output, raw_response=context, ) def alerts_rules_get_command(client: ExtraHopClient) -> CommandResults: """Retrieve all the available alerts from the ExtraHop Reveal(X). Args: client: ExtraHop client to be used. Returns: CommandResult object """ result = client.get_alert_rules() readable_output = prepare_alert_rules_get_output(result) return CommandResults( outputs_prefix="ExtraHop.Alert", outputs_key_field="id", outputs=remove_empty_elements_from_response(result), readable_output=readable_output, raw_response=result, ) def packets_search_command(client: ExtraHopClient, args: Dict[str, Any]) -> Union[str, Dict]: """Retrieve the specific packets from Reveal(X). Args: client: ExtraHop client to be used. args: Arguments obtained from demisto.args(). Returns: File containing the packets. """ output = args.get("output", "pcap") limit_bytes = args.get("limit_bytes", "10MB") limit_search_duration = args.get("limit_search_duration", "5m") query_from = args.get("query_from", "-10m") query_until = args.get("query_until") bpf = args.get("bpf") ip1 = args.get("ip1") port1 = args.get("port1") ip2 = args.get("ip2") port2 = args.get("port2") validate_packets_search_arguments(output) response = client.packets_search( output, limit_bytes, limit_search_duration, query_from, query_until, bpf, ip1, port1, ip2, port2 ) if response.status_code == 204: return "Search matched no packets." filename_header = response.headers.get("content-disposition") f_attr = "filename=" if filename_header and f_attr in filename_header: quoted_filename = filename_header[filename_header.index(f_attr) + len(f_attr) :] filename = quoted_filename.replace('"', "") else: raise DemistoException("Error filename could not be found in response header.") return fileResult(filename, response.content) def ticket_track_command(client: ExtraHopClient, args: Dict[str, Any]) -> CommandResults: """Link a Reveal(x) detection to a Demisto Investigation. Args: client: ExtraHop client to be used. args: Arguments received for the command. Returns: CommandResult object """ incident_id = args.get("incident_id") detection_id = args.get("detection_id", "") incident_owner = args.get("incident_owner") incident_status = args.get("incident_status", "") incident_close_reason = args.get("incident_close_reason") validate_ticket_track_arguments(incident_status) detection_status = { "ticket_id": incident_id, "status": TICKET_STATUS_MAP.get(incident_status), "assignee": incident_owner or None, } # Only set Resolution if the incident is closed if detection_status["status"] == "closed" and incident_close_reason: if incident_close_reason == "Resolved": detection_status["resolution"] = "action_taken" elif incident_close_reason in {"False Positive", "Duplicate"}: detection_status["resolution"] = "no_action_taken" client.patch_detections(detection_id, detection_status) readable_output = f"Successfully linked detection({detection_id}) with incident({incident_id})" output = {"TicketId": incident_id} # type: dict return CommandResults( outputs_prefix="ExtraHop", outputs_key_field="TicketId", outputs=remove_empty_elements(output), readable_output=readable_output, raw_response=output, ) def devices_tag_command(client: ExtraHopClient, args: Dict[str, Any]) -> str: """Add or remove a tag from devices in Reveal(x). Args: client: ExtraHop client to be used. args: Arguments received for the command. Returns: CommandResult object """ tag = args.get("tag", "") add = args.get("add", "") remove = args.get("remove", "") validate_add_and_remove_arguments(add, remove) devices_tag(client, tag, add, remove) return "Successfully tagged untagged the device/s." def create_or_edit_alert_rule_command(client: ExtraHopClient, args: Dict[str, Any]) -> CommandResults: """Create or update alert rule from Reveal(x). Args: client: ExtraHop client to be used. args: Arguments received for the command. Returns: CommandResult object. """ apply_all = argToBoolean(args.get("apply_all", False)) disabled = argToBoolean(args.get("disabled", False)) name = args.get("name") notify_snmp = argToBoolean(args.get("notify_snmp", False)) refire_interval = args.get("refire_interval", 0) severity = args.get("severity", 0) alert_type = args.get("type") alert_id = args.get("alert_id") validate_create_or_update_alert_rule_arguments(refire_interval, severity, alert_type) # Prepare request body for alert-rule. data = { "apply_all": apply_all, "disabled": disabled, "name": name, "notify_snmp": notify_snmp, "refire_interval": int(refire_interval), "severity": int(severity), "type": alert_type, } # For alertType = threshold. if alert_type == "threshold": field_name = args.get("field_name") field_name2 = args.get("field_name2") field_op = args.get("field_op") interval_length = args.get("interval_length", 0) operand = args.get("operand") operator = args.get("operator") param = args.get("param") param2 = args.get("param2") stat_name = args.get("stat_name") units = args.get("units") validate_threshold_alert_rule_arguments(interval_length, operator, units) data["interval_length"] = int(interval_length) data["operator"] = operator data["units"] = units if field_name: data["field_name"] = field_name if field_name2: data["field_name2"] = field_name2 if field_op: data["field_op"] = field_op if operand: data["operand"] = operand if param: data["param"] = param if param2: data["param2"] = param2 if stat_name: data["stat_name"] = stat_name elif alert_type == "detection": if object_type := args.get("object_type"): if object_type not in VALID_ALERT_RULE_OBJECT_TYPES: raise InvalidValueError("object_type", object_type, VALID_ALERT_RULE_OBJECT_TYPES) data["object_type"] = object_type if protocols := argToList(args.get("protocols")): data["protocols"] = protocols if alert_id: client.update_alert_rule(alert_id, data) return CommandResults(readable_output="Successfully updated alert rule.") else: client.create_alert_rule(data) return CommandResults(readable_output="Successfully created alert rule.") def watchlist_edit_command(client: ExtraHopClient, args: Dict[str, Any]) -> CommandResults: """Add or remove devices from the watchlist in Reveal(x). Args: client: ExtraHop client to be used. args: Arguments obtained from demisto.args(). Returns: CommandResults object. """ add = args.get("add", "") remove = args.get("remove", "") validate_add_and_remove_arguments(add, remove) body = {} hr_outputs = "" if add: body["assign"] = get_devices_by_ip_or_id(client, add, id_only=True) hr_outputs += f"Successfully added new devices({add}) in the watchlist \n" if remove: hr_outputs += f"Successfully removed devices({remove}) from the watchlist" body["unassign"] = get_devices_by_ip_or_id(client, remove, id_only=True) client.edit_watchlist(body) return CommandResults(readable_output=hr_outputs) def metrics_list_command(client: ExtraHopClient, args=None, advanced_filter: str = None) -> CommandResults: """Retrieve metric information collected about every object from the Reveal(X). Args: client: ExtraHop client to be used. args: Arguments obtained from demisto.args() advanced_filter: Advance filter to be used for fetching incidents. Returns: CommandResult object """ # This snipper will work in case of the fetch incidents for metrics, when user provides advance filter. if args is None: args = {} if advanced_filter: try: body = json.loads(advanced_filter) # type: ignore except json.JSONDecodeError: raise ValueError("Invalid json string provided for advanced filter.") validate_metrics_list_arguments(body) else: cycle = args.get("cycle") from_time = args.get("from_time") metric_category = args.get("metric_category") object_ids = argToList(args.get("object_ids")) object_ids = [arg_to_number(object_id) for object_id in object_ids] object_type = args.get("object_type") until_time = args.get("until_time") metric_specs = args.get("metric_specs") try: metric_specs_json = json.loads(metric_specs) # type: ignore except json.JSONDecodeError: raise ValueError("Invalid JSON string provided for metric specs.") body = { "cycle": cycle, "from": from_time, "until": until_time, "metric_category": metric_category, "metric_specs": metric_specs_json, "object_ids": object_ids, "object_type": object_type, } validate_metrics_list_arguments(body) response = client.metrics_list(body) readable_output = prepare_metrics_list_output(response) return CommandResults( outputs_prefix="ExtraHop.Metrics", outputs=remove_empty_elements_from_response(response), readable_output=readable_output, raw_response=response, ) def detections_list_command(client: ExtraHopClient, args: Dict[str, Any], on_cloud=False, advanced_filter=None) -> CommandResults: """Retrieve the detections from Reveal(X). Args: client: ExtraHop client to be used. args: Arguments obtained from demisto.args(). advanced_filter: The advanced filter provided by user to fetch detections. on_cloud: Check if ExtraHop instance is on cloud. Returns: CommandResults object. """ version = get_extrahop_server_version(client) if not is_version_greater_or_equal(version, MINIMUM_FIRMWARE_VERSION): raise DemistoException( "This integration works with ExtraHop firmware version greater than or equal to " f"{MINIMUM_FIRMWARE_VERSION} and current firmware version being used by you is {version}" ) body = {} if advanced_filter: body = advanced_filter else: filter_query = args.get("filter") from_time = arg_to_number(args.get("from")) limit = arg_to_number(args.get("limit"), "200") offset = arg_to_number(args.get("offset")) sort = args.get("sort") until_time = arg_to_number(args.get("until")) mod_time = arg_to_number(args.get("mod_time")) if filter_query and filter_query.strip(): try: filter_query = json.loads(filter_query) add_default_category_for_filter_of_detection_list(filter_query) body["filter"] = filter_query except json.JSONDecodeError: raise ValueError("Invalid json string provided for filter.") else: body["filter"] = {"categories": ["sec.attack"]} if isinstance(from_time, int): body["from"] = from_time if isinstance(limit, int): body["limit"] = limit if isinstance(offset, int): body["offset"] = offset if sort: sort_list = [] sort_on_field = sort.split(",") for sort in sort_on_field: try: field, direction = sort.split(" ") except ValueError: raise DemistoException( 'Incorrect input provided for argument "sort". Please follow the format mentioned in description.' ) if direction not in SORT_DIRECTION: raise DemistoException( 'Incorrect input provided for argument "sort". Allowed values for ' "direction are: " + ", ".join(SORT_DIRECTION) ) prepared_sort_dict = {"direction": direction, "field": field} sort_list.append(prepared_sort_dict) body["sort"] = sort_list if isinstance(until_time, int): body["until"] = until_time if isinstance(mod_time, int): body["mod_time"] = mod_time validate_detections_list_arguments(body) detections = client.detections_list(body) base_url = client._base_url if on_cloud: base_url = remove_api_from_base_url(base_url) for detection in detections: if detection.get("description"): detection["description"] = modify_description(base_url, detection.get("description")) readable_output = prepare_list_detections_output(detections) return CommandResults( outputs_prefix="ExtraHop.Detections", outputs_key_field="id", outputs=remove_empty_elements(detections), readable_output=readable_output, raw_response=detections, ) def get_mirroring() -> Dict: """Add mirroring related keys in an incident. Returns: A dictionary containing required key-value pairs for mirroring. """ return { "mirror_direction": "In", "mirror_instance": demisto.integrationInstance(), } def get_modified_remote_data_command(client, args: Dict[str, Any], params: Dict) -> GetModifiedRemoteDataResponse: """Retrieve the IDs of the incidents which are updated since the last updated. Args: client: XSOAR client to use. args: lastUpdate: When was the last time we retrieved data. params: The integration configuration parameters. Returns: GetModifiedRemoteDataResponse: List of incidents IDs which are modified since the last update. """ # Retrieve the arguments passed with the command. command_args = GetModifiedRemoteDataArgs(args) # Parse the last update date got from the command arguments. command_last_run_date = dateparser.parse(command_args.last_update, settings={"TIMEZONE": "UTC"}) demisto.debug(f"Last update date of get-modified-remote-data command is {command_last_run_date}.") # Convert the datetime object to epoch as the API requires the time in epoch format. body = { "mod_time": date_to_timestamp(command_last_run_date), # End time for the API call will be current time. "until": 0, "offset": 0, "sort": [{"direction": "asc", "field": "mod_time"}], "limit": MAX_FETCH, } advanced_filter = params.get("advanced_filter") try: advanced_filter = json.loads(advanced_filter) # type: ignore except json.JSONDecodeError as error: raise ValueError("Invalid JSON string provided for advanced filter.") from error body["filter"] = advanced_filter len_of_incidents = 0 updated_incident_ids = [] while True: body["offset"] += len_of_incidents list_incidents_resp = client.detections_list(body) len_of_incidents = len(list_incidents_resp) if len_of_incidents == 0: break # Extract the IDs of the incidents. updated_incident_ids.extend([str(inc.get("id")) for inc in list_incidents_resp]) if len(updated_incident_ids) >= 10000: break # Filter out None values if there are any. updated_incident_ids: List[str] = list(filter(None, updated_incident_ids)) # Filter out any duplicate incident IDs. updated_incident_ids = list(set(updated_incident_ids)) # At max 10,000 incidents should be updated. updated_incident_ids = updated_incident_ids[:10000] demisto.info( f'Extrahop Number of incidents modified between {body["mod_time"]} to {body["until"]} are ' f'{len(updated_incident_ids)}.' ) demisto.info( f'Extrahop List of modified incident ids between {body["mod_time"]} to {body["until"]} is {updated_incident_ids}.' ) return GetModifiedRemoteDataResponse(updated_incident_ids) def get_remote_data_command(client, args: Dict[str, Any]) -> Union[str, GetRemoteDataResponse]: """Return the updated incident and updated entries. Args: client: XSOAR client to use. args: id: Incident ID to retrieve. Returns: First entry is the incident (which can be completely empty) and the new entries. """ parsed_args = GetRemoteDataArgs(args) demisto.info(f"Extrahop get_remote_data_command invoked for {parsed_args.remote_incident_id}") detection = client.get_detections_by_id(parsed_args.remote_incident_id) return GetRemoteDataResponse(detection, []) def test_module(client: ExtraHopClient) -> str: """Tests API connectivity and authentication. Returning "ok" indicates that the integration works like it is supposed to. Connection to the service is successful. Args: client: ExtraHop client to be used. Returns: "ok" if test passed, anything else will fail the test. """ response = client.test_connection() version = get_extrahop_server_version(client) if not is_version_greater_or_equal(version, MINIMUM_FIRMWARE_VERSION): raise DemistoException( "This integration works with ExtraHop firmware version greater than or equal to " f"{MINIMUM_FIRMWARE_VERSION} and current firmware version being used by you is {version}" ) if response: return "ok" raise ValueError("Failed to establish connection with provided credentials.") def prepare_query_records_output(response: Dict) -> str: """ prepare human-readable output for query-records command Args: response: List of records response from the API Returns: markdown string to be displayed in the war room """ records = response.get("records", []) hr_table = [] extrahop_context: Dict[str, Any] = {"ExtraHop": {"Record": []}} for record in records: hr_table.append(record["_source"]) extrahop_context["ExtraHop"]["Record"].append(createContext(record, keyTransform=string_to_context_key, removeNull=True)) if len(records) == 0: return "No records were found" return tableToMarkdown( "showing {results} out of {total} Record(s) Found.".format(total=response.get("total", 0), results=len(records)), hr_table ) def query_records_command(client: ExtraHopClient, args: Dict[str, Any]) -> CommandResults: """Query records command is fetched records in the given timeline from the ExtraHop instance. Args: client: ExtraHop client to be used. args: Arguments received for the command. Returns: Command Result object. """ query_from = args.get("query_from") query_until = args.get("query_until") limit = args.get("limit") offset = args.get("offset") field1 = args.get("field1") operator1 = args.get("operator1") value1 = args.get("value1") field2 = args.get("field2") operator2 = args.get("operator2") value2 = args.get("value2") match_type = args.get("match_type", "") specific_types = args.get("types") data: Dict = {} if specific_types: try: data["types"] = [f"~{rec_type.strip()}" for rec_type in specific_types.split(",")] except Exception: raise DemistoException("Error parsing the types argument, expected a comma separated list of types.") if query_from: data["from"] = query_from if query_until: data["until"] = query_until if limit: if int(limit) > 1000: data["limit"] = 1000 data["context_ttl"] = "30s" else: data["limit"] = int(limit) if offset: data["offset"] = int(offset) data["filter"] = {"operator": match_type, "rules": []} if field1: rule = {"field": field1, "operator": operator1, "operand": value1 or ""} data["filter"]["rules"].append(rule) if field2: rule = {"field": field2, "operator": operator2, "operand": value2 or ""} data["filter"]["rules"].append(rule) result = client.get_query_records(body=data) cursor = result.get("cursor") if cursor and result.get("total", 0) > data["limit"]: additional_records = client.get_next_page_records(cursor).get("records", []) while (len(additional_records)) > 0: result["records"].extend(additional_records) additional_records = client.get_next_page_records(cursor).get("records", []) readable_output = prepare_query_records_output(result) return CommandResults( outputs_prefix="ExtraHop.Record", outputs_key_field="id", outputs=remove_empty_elements_from_response(result), readable_output=readable_output, raw_response=result, ) def main(): """Parse params and runs command functions.""" command = demisto.command() params = demisto.params() args = demisto.args() try: on_cloud = params.get("on_cloud", False) api_key = params.get("apikey") base_url = params.get("url").strip("/") client_id = params.get("client_id", "") client_secret = params.get("client_secret", "") verify_certificate = not params.get("insecure", False) use_proxy: bool = params.get("proxy", False) if on_cloud and (not client_id or not client_secret): raise DemistoException("If On Cloud is marked true, Client ID and Client Secret is required field.") client = ExtraHopClient( base_url=base_url, api_key=api_key, client_id=client_id, client_secret=client_secret, verify=verify_certificate, use_proxy=use_proxy, ok_codes=(200, 201, 204), on_cloud=on_cloud, ) remove_nulls_from_dictionary(trim_spaces_from_args(args)) demisto.info(f"Extrahop command being called is {command}") if command == "test-module": return_results(test_module(client)) elif command == "fetch-incidents": last_run = demisto.getLastRun() incidents, next_run = fetch_incidents(client, params, last_run, on_cloud) demisto.setLastRun(next_run) demisto.incidents(incidents) elif command == "extrahop-watchlist-get": return_results(watchlist_get_command(client, on_cloud)) elif command == "extrahop-peers-get": return_results(peers_get_command(client, args, on_cloud)) elif command == "extrahop-devices-search": return_results(devices_search_command(client, args, on_cloud)) elif command == "extrahop-protocols-get": return_results(protocols_get_command(client, args, on_cloud)) elif command == "extrahop-activity-map-get": return_results(activity_map_get_command(client, args, on_cloud)) elif command == "extrahop-alert-rules-get": return_results(alerts_rules_get_command(client)) elif command == "extrahop-packets-search": return_results(packets_search_command(client, args)) elif command == "extrahop-ticket-track": return_results(ticket_track_command(client, args)) elif command == "extrahop-devices-tag": return_results(devices_tag_command(client, args)) elif command in ("extrahop-alert-rule-create", "extrahop-alert-rule-edit"): return_results(create_or_edit_alert_rule_command(client, args)) elif command == "extrahop-watchlist-edit": return_results(watchlist_edit_command(client, args)) elif command == "extrahop-metrics-list": return_results(metrics_list_command(client, args)) elif command == "extrahop-detections-list": return_results(detections_list_command(client, args, on_cloud)) elif command == "get-remote-data": return_results(get_remote_data_command(client, args)) elif command == "get-modified-remote-data": return_results(get_modified_remote_data_command(client, args, params)) # Deprecated commands. elif demisto.command() in ("extrahop-get-alert-rules", "extrahop-get-alerts"): return_results(alerts_rules_get_command(client)) elif demisto.command() == "extrahop-query-records": # Removed this command. return_results(query_records_command(client, args)) elif demisto.command() == "extrahop-device-search": return_results(devices_search_command(client, args, on_cloud)) elif demisto.command() == "extrahop-edit-watchlist": return_results(watchlist_edit_command(client, args)) elif demisto.command() == "extrahop-get-watchlist": return_results(watchlist_get_command(client, on_cloud)) elif demisto.command() in ("extrahop-create-alert-rule", "extrahop-create-alert"): return_results(create_or_edit_alert_rule_command(client, args)) elif demisto.command() in ("extrahop-edit-alert-rule", "extrahop-edit-alert"): return_results(create_or_edit_alert_rule_command(client, args)) elif demisto.command() == "extrahop-track-ticket": return_results(ticket_track_command(client, args)) elif demisto.command() == "extrahop-get-peers": return_results(peers_get_command(client, args, on_cloud)) elif demisto.command() == "extrahop-get-protocols": return_results(protocols_get_command(client, args, on_cloud)) elif demisto.command() == "extrahop-tag-devices": return_results(devices_tag_command(client, args)) elif demisto.command() == "extrahop-get-activity-map": return_results(activity_map_get_command(client, args, on_cloud)) elif demisto.command() == "extrahop-search-packets": return_results(packets_search_command(client, args)) else: raise NotImplementedError(f"Command {command} is not implemented.") except Exception as error: demisto.error(traceback.format_exc()) return_error(f"Failed to execute {command} command.\nError:\n{error!s}") # noqa: E231 if __name__ in ("__main__", "__builtin__", "builtins"): # pragma: no cover main()