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).
"""Test File for ExtraHop Integration.""" import datetime import json import os from copy import copy from unittest import mock import ExtraHop_v2 import pytest from CommonServerPython import DemistoException BASE_URL = "https://dummy-base-url.com" API_KEY = "dummy-api-key" CLIENT_ID = "dummy-client-id" CLIENT_SECRET = "dummy-client-secret" EXTRAHOP_DEVICE = "ExtraHop.Device" GET_PROTOCOL_FILE = "get_protocol_get_peers.json" GET_PROTOCOL_NETWORKS_FILE = "get_protocol_networks.json" PACKETS_SEARCH_URL = "/api/v1/packets/search" GET_ALL_TAGS_URL = "/api/v1/tags" TAG_DEVICE_URL = "/api/v1/tags/12/devices" INVALID_AUTH_HEADER = "Authorization header is invalid" LIST_DETECTIONS_SUCCESS = "list_detections_success.json" def get_packets_search_args(): """ A common function to return arguments for packet-search command. """ return { "output": "zip", "limit_bytes": "10MB", "limit_search_duration": "5m", "query_from": "-10m", "query_until": "now", "bpf": "ip[1] != 0", "ip1": "0.0.0.0", "port1": "8080", "ip2": "0.0.0.0", "port2": "8081", } def get_device_tag_args(): """ A common function to return arguments for devices-tag command. """ return {"tag": "MyTag", "add": "1,2", "remove": "3,4"} # pytest fixture for testing arguments of a function and calling # BaseClient's "_http_request" method. @pytest.fixture def argtest(): def _argtest(**_kwargs): class TestArgs: def __call__(self, *args, **kwargs): self.args = list(args) self.kwargs = kwargs return _kwargs["_http_request"](*args, **kwargs) return TestArgs() return _argtest @pytest.fixture def get_devices_by_ip_or_id_fixture(mocker): """ A pytest fixture to mock the "get_devices_by_ip_or_id" function. Args: mocker: An object to mock specific function of ExtraHop_v2 module. """ return mocker.patch("ExtraHop_v2.get_devices_by_ip_or_id", side_effect=[[1, 2], [3, 4]]) def init_mock_client(requests_mock, on_cloud): """ Extrahop mock client based on on_cloud param. Args: requests_mock: Mock object of the request. on_cloud (bool): whether client created on cloud or on-prem instance. """ if on_cloud: access_token_response = load_mock_response("auth_token.json") requests_mock.post("/oauth2/token", json=access_token_response) return ExtraHop_v2.ExtraHopClient( base_url=BASE_URL, api_key=API_KEY, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, verify=True, use_proxy=False, ok_codes=(200, 201, 204), on_cloud=on_cloud, ) def load_mock_response(file_name: str) -> dict: """ Load one of the mock responses to be used for assertion. Args: file_name (str): Name of the mock response JSON file to return. """ with open( os.path.join(os.path.dirname(__file__), f"test_data/{file_name}"), encoding="utf-8", ) as json_file: return json.loads(json_file.read()) def load_file(file_name: str) -> str: """ Load file to be used for assertion. Args: file_name (str): Name of the mock response JSON file to return. """ with open(os.path.join(os.path.dirname(__file__), f"test_data/{file_name}")) as file: return file.read() @pytest.mark.parametrize( "on_cloud, hr_output", [ (False, load_file("watchlist_get_hr.md")), (True, load_file("watchlist_get_command_on_cloud.md")), ], ) def test_watchlist_get_command(on_cloud, hr_output, requests_mock) -> None: """ Test case scenario for successful execution of watchlist-get command. Given: - User has provided valid credentials. When: - watchlist_get_command is called. Then: - Ensure number of items is correct. - Ensure outputs prefix is correct. """ watchlist_mock_response = load_mock_response("watchlist_get.json") requests_mock.get("/api/v1/watchlist/devices", json=watchlist_mock_response) network_mock_response = load_mock_response("network_get.json") requests_mock.get("/api/v1/networks", json=network_mock_response) client = init_mock_client(requests_mock, on_cloud) result = ExtraHop_v2.watchlist_get_command(client, on_cloud) assert len(result.outputs) == 3 # type: ignore assert result.outputs_prefix == EXTRAHOP_DEVICE assert result.readable_output == hr_output @pytest.mark.parametrize("on_cloud", [False, True]) def test_create_or_edit_alert_rule_command(on_cloud, requests_mock) -> None: """ Test case scenario for successful execution of create-or-edit-alert-rule command. Given: - User has provided valid credentials. When: - create_or_edit_alert_rule_command is called. Then: - Ensure number of items is correct. - Ensure outputs prefix is correct. """ alert_mock_response = load_mock_response("/alerts/create_alert.json") requests_mock.post("/api/v1/alerts", status_code=201) client = init_mock_client(requests_mock, on_cloud) result = ExtraHop_v2.create_or_edit_alert_rule_command(client, alert_mock_response) assert result.readable_output == "Successfully created alert rule." @pytest.mark.parametrize("on_cloud", [False, True]) def test_create_or_edit_alert_rule_command_invalid_type(on_cloud, requests_mock) -> None: """ Test case scenario for invalid type of create-or-edit-alert-rule command. Given: - User has provided valid credentials. When: - create_or_edit_alert_rule_command is called. Then: - Ensure appropriate error is raised. """ alert_mock_response = load_mock_response("/alerts/create_alert_validation.json") client = init_mock_client(requests_mock, on_cloud) with pytest.raises(ExtraHop_v2.InvalidValueError): ExtraHop_v2.create_or_edit_alert_rule_command(client, alert_mock_response) @pytest.mark.parametrize("on_cloud", [False, True]) def test_create_or_edit_alert_rule_command_invalid_object_type(on_cloud, requests_mock) -> None: """ Test case scenario for invalid object type of create-or-edit-alert-rule command. Given: - User has provided valid credentials. When: - Create_or_edit_alert_rule_command is called. Then: - Ensure appropriate error is raised. """ alert_mock_response = load_mock_response("/alerts/create_alert_validation_object_type.json") client = init_mock_client(requests_mock, on_cloud) with pytest.raises(ExtraHop_v2.InvalidValueError): ExtraHop_v2.create_or_edit_alert_rule_command(client, alert_mock_response) @pytest.mark.parametrize( ("refire_interval", "severity", "on_cloud"), [["10", "1", True], ["300", "8", True], ["10", "1", False], ["300", "8", False]], ) def test_create_or_edit_alert_rule_command_invalid_rule_arguments(on_cloud, refire_interval, severity, requests_mock) -> None: """ Test case scenario for invalid rule arguments of create-or-edit-alert-rule command. Given: - User has provided valid credentials. When: - create_or_edit_alert_rule_command is called. Then: - Ensure appropriate error is raised. """ alert_mock_response = {"refire_interval": refire_interval, "severity": severity} client = init_mock_client(requests_mock, on_cloud) with pytest.raises(ExtraHop_v2.InvalidValueError): ExtraHop_v2.create_or_edit_alert_rule_command(client, alert_mock_response) @pytest.mark.parametrize( ("interval_length", "operator", "units", "on_cloud"), [ ["31", "==", "none", True], ["30", "!=", "none", True], ["30", "==", "nones", True], ["31", "==", "none", False], ["30", "!=", "none", False], ["30", "==", "nones", False], ], ) def test_create_or_edit_alert_rule_command_invalid_thresold_argument( on_cloud, interval_length, operator, units, requests_mock ) -> None: """ Test case scenario for validate type of create-or-edit-alert-rule command. Given: - User has provided valid credentials. When: - create_or_edit_alert_rule_command is called. Then: - Ensure appropriate error is raised. """ alert_mock_response = { "type": "threshold", "interval_length": interval_length, "operator": operator, "units": units, } client = init_mock_client(requests_mock, on_cloud) with pytest.raises(ExtraHop_v2.InvalidValueError): ExtraHop_v2.create_or_edit_alert_rule_command(client, alert_mock_response) def test_update_alert_rule_command(requests_mock) -> None: """ Test case scenario for create-or-edit-alert-rule command. Given: - User has provided valid credentials. When: - create_or_edit_alert_rule_command is called. Then: - Ensure number of items is correct. - Ensure outputs prefix is correct. """ alert_id = 212 alert_mock_response = { "type": "threshold", "interval_length": "30", "operator": "==", "units": "none", "alert_id": alert_id, } client = init_mock_client(requests_mock, on_cloud=False) requests_mock.patch(f"/api/v1/alerts/{alert_id}", status_code=204) result = ExtraHop_v2.create_or_edit_alert_rule_command(client, alert_mock_response) assert result.readable_output == "Successfully updated alert rule." @pytest.mark.parametrize( "args, error_msg", [ ( { "cycle": "dummy", "object_type": "device", "from": "-30m", "until": "-20m", "metric_category": "http", "objects_ids": [0], "metric_specs": '[{"name": "rsp"}]', }, f"dummy is an invalid value for cycle. Possible values are: {ExtraHop_v2.VALID_CYCLES}", ), ( { "cycle": "auto", "object_type": "dummy", "from": "-30m", "until": "-20m", "metric_category": "http", "objects_ids": [0], "metric_specs": '[{"name": "rsp"}]', }, f"dummy is an invalid value for object_type. Possible values are: {ExtraHop_v2.VALID_OBJECT_TYPES}", ), ( { "cycle": "auto", "object_type": "device", "from": "-30m", "until": "-20m", "metric_category": "http", "objects_ids": [0], "metric_specs": "{'test': dummy}", }, "Invalid JSON string provided for metric specs.", ), ], ) def test_metrics_list_command_invalid_args(requests_mock, args, error_msg): """Test case scenario for invalid arguments while execution of metrics-list command. Given: - User has provided invalid arguments. When: - metrics_list_command is called. Then: - Ensure appropriate error is raised. """ client = init_mock_client(requests_mock, on_cloud=False) with pytest.raises(Exception) as error: ExtraHop_v2.metrics_list_command(client, args) assert str(error.value) == error_msg def test_metrics_list_command_successful_execution(requests_mock): """Test case scenario for successful execution of metrics-list command. Given: - User has provided valid credentials. When: - metrics_list_command is called. Then: - Ensure human-readable output is correct. - Ensure outputs prefix is correct. """ client = init_mock_client(requests_mock, on_cloud=False) args = { "cycle": "24hr", "from": "-30m", "until": "0", "metric_category": "http", "metric_specs": '[{"name": "rsp"}]', "object_ids": [0], "object_type": "network", } response = load_mock_response("metrics_list_success.json") expected_hr = load_file("metrics_list_success_hr.md") requests_mock.post(f"{BASE_URL}/api/v1/metrics", json=response) results = ExtraHop_v2.metrics_list_command(client, args=args) assert results.readable_output == expected_hr assert results.outputs_prefix == "ExtraHop.Metrics" def test_metrics_list_command_using_advanced_filter(requests_mock): """Test case scenario for successful execution of metrics-list command using advanced_filter argument. Given: - User has provided valid credentials. When: - metrics_list_command is called. Then: - Ensure human-readable output is correct. - Ensure outputs prefix is correct. """ client = init_mock_client(requests_mock, on_cloud=False) advanced_filter = """{\"cycle\": \"auto\", \"from\": \"-30m\", \"metric_category\": \"http\", \"metric_specs\": [{\"name\":\"rsp\"}], \"object_ids\": [0], \"object_type\": \"application\", \"until\": \"0\"}""" response = load_mock_response("metrics_list_success.json") expected_hr = load_file("metrics_list_success_hr.md") requests_mock.post(f"{BASE_URL}/api/v1/metrics", json=response) results = ExtraHop_v2.metrics_list_command(client, advanced_filter=advanced_filter) assert results.readable_output == expected_hr def test_metrics_list_commands_using_advance_filter_invalid_args(requests_mock): """Test case scenario for failure of metrics-list command using invalid advanced_filter argument. Given: - User has provided valid credentials. When: - metrics_list_command is called. Then: - Ensure human-readable output is correct. - Ensure outputs prefix is correct. """ client = init_mock_client(requests_mock, on_cloud=False) advanced_filter = """{\"cycles\": \"auto\", \"from\": \"-30m\", \"metric_category\": \"http\", \"metric_specs\": [{\"name\":\"rsp\"}], \"object_ids\": [0], \"object_type\": \"application\", \"until\": \"0\"}""" response = load_mock_response("metrics_list_success.json") requests_mock.post(f"{BASE_URL}/api/v1/metrics", json=response) with pytest.raises(Exception) as error: ExtraHop_v2.metrics_list_command(client, advanced_filter=advanced_filter) assert ( str(error.value) == "cycles is an invalid value for keys. Possible values are: ['cycle', 'from', " "'metric_category', 'metric_specs', 'object_ids', 'object_type', 'until']" ) def test_metrics_list_command_error_code_400(requests_mock): """Test case scenario for failure metrics-list command when status code is 400. Given: - User has provided invalid json input. When: - metrics_list_command is called. Then: - Ensure error is raised with error code. """ client = init_mock_client(requests_mock, on_cloud=False) args = { "cycle": "24hr", "from": "-30m", "until": "0", "metric_category": "http", "metric_specs": '[{"name": "rsp"}]', "object_ids": [0], "object_type": "network", } response = { "type": "invalid_request", "detail": "The JSON payload specified in the request is invalid.", } requests_mock.post(f"{BASE_URL}/api/v1/metrics", json=response, status_code=400) with pytest.raises(Exception) as error: ExtraHop_v2.metrics_list_command(client, args) assert ( str(error.value) == 'Error in API call [400] - None\n{"type": "invalid_request", "detail": "The JSON ' 'payload specified in the request is invalid."}' ) def test_metrics_list_command_error_code_401(requests_mock): """Test case scenario for failure metrics-list command when status code is 401. Given: - User has provided invalid json input. When: - metrics_list_command is called. Then: - Ensure error is raised with error code. """ client = init_mock_client(requests_mock, on_cloud=False) args = { "cycle": "24hr", "from": "-30m", "until": "0", "metric_category": "http", "metric_specs": '[{"name": "rsp"}]', "object_ids": [0], "object_type": "network", } response = {"error_message": "API Key is invalid"} requests_mock.post(f"{BASE_URL}/api/v1/metrics", json=response, status_code=401) with pytest.raises(Exception) as error: ExtraHop_v2.metrics_list_command(client, args) assert str(error.value) == 'Error in API call [401] - None\n{"error_message": "API Key is invalid"}' @pytest.mark.parametrize( "args, error_msg", [ ({"from": "123s"}, '"123s" is not a valid number'), ( {"limit": 0}, "Invalid input for field limit. It should have numeric value greater than zero.", ), ( {"offset": -1}, "Invalid input for field offset. It should have numeric value greater than or equal to zero.", ), ( {"from": "1673508360001", "until": "1673508360000"}, 'Input for "from" should always be less than that of "until".', ), ( {"from": "-1673508360001", "until": "-1673508360002"}, 'Input for "from" should always be less than that of "until".', ), ({"filter": "{'limit': 1}"}, "Invalid json string provided for filter."), ( {"sort": "id asc desc"}, 'Incorrect input provided for argument "sort". Please follow the format mentioned in description.', ), ( {"sort": "id asd"}, 'Incorrect input provided for argument "sort". Allowed values for direction are: asc, desc', ), ( {"filter": '{"invalid_arg": 1}'}, "invalid_arg is an invalid value for key. Possible values are: ['assignee', " "'categories', 'category', 'resolution', 'risk_score_min', 'status', 'ticket_id', 'types']", ), ], ) def test_detections_list_command_invalid_args(requests_mock, args, error_msg): """Test case scenario for invalid arguments while execution of detections list command. Given: - User has provided invalid arguments. When: - detections_list_command is called. Then: - Ensure appropriate error is raised. """ requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"}) client = init_mock_client(requests_mock, on_cloud=False) with pytest.raises(Exception) as error: ExtraHop_v2.detections_list_command(client, args) assert str(error.value) == error_msg def test_detections_list_command_failure_when_firmware_version_is_outdated(requests_mock): """Test case scenario for execution of detections list command when ExtraHop firmware version is less than 9.3.0. Given: - Arguments for detections list command. When: - detections_list_command is called. Then: - Returns a valid error message. """ client = init_mock_client(requests_mock, on_cloud=False) requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.1"}) with pytest.raises(DemistoException) as err: ExtraHop_v2.detections_list_command(client, {}, True, "{}") assert ( str(err.value) == "This integration works with ExtraHop firmware version greater than or equal to 9.3.0 " "and current firmware version being used by you is 9.1" ) @pytest.mark.parametrize("on_cloud", [False, True]) def test_list_detections_command_successful_execution_with_categories(on_cloud, requests_mock): """Test case scenario for successful execution of detections-list command with categories. Given: - User has provided valid credentials. When: - detections_list_command is called. Then: - Ensure human-readable output is correct. - Ensure outputs prefix is correct. """ requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.4"}) client = init_mock_client(requests_mock, on_cloud) args = { "limit": "2", "filter": """{ \"categories\": [\"sec.attack\"], \"risk_score_min\": 51 }""", "from": "1573500360001", "offset": "2", "sort": "end_time asc,id desc", "until": "1673569370001", } response = load_mock_response(LIST_DETECTIONS_SUCCESS) expected_hr = load_file("list_detections_success_hr.md") requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=response) results = ExtraHop_v2.detections_list_command(client, args) assert results.readable_output == expected_hr assert results.outputs_prefix == "ExtraHop.Detections" @pytest.mark.parametrize("on_cloud", [False, True]) def test_list_detections_command_successful_execution_with_category(on_cloud, requests_mock): """Test case scenario for successful execution of detections-list command with category. Given: - User has provided valid credentials. When: - detections_list_command is called. Then: - Ensure human-readable output is correct. - Ensure outputs prefix is correct. """ requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"}) client = init_mock_client(requests_mock, on_cloud) args = { "limit": "2", "filter": """{ \"category\": \"sec.attack\", \"risk_score_min\": 51 }""", "from": "1573500360001", "offset": "2", "sort": "end_time asc,id desc", "until": "1673569370001", } response = load_mock_response(LIST_DETECTIONS_SUCCESS) expected_hr = load_file("list_detections_success_hr.md") requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=response) results = ExtraHop_v2.detections_list_command(client, args) assert results.readable_output == expected_hr assert results.outputs_prefix == "ExtraHop.Detections" @pytest.mark.parametrize("on_cloud", [False, True]) def test_list_detections_command_successful_execution_without_category(on_cloud, requests_mock): """Test case scenario for successful execution of detections-list command without categories. Given: - User has provided valid credentials. When: - detections_list_command is called. Then: - Ensure human-readable output is correct. - Ensure outputs prefix is correct. """ requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"}) client = init_mock_client(requests_mock, on_cloud) args = { "limit": "2", "filter": """{ \"risk_score_min\": 51 }""", "from": "1573500360001", "offset": "2", "sort": "end_time asc,id desc", "until": "1673569370001", } response = load_mock_response(LIST_DETECTIONS_SUCCESS) expected_hr = load_file("list_detections_success_hr.md") requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=response) results = ExtraHop_v2.detections_list_command(client, args) assert results.readable_output == expected_hr assert results.outputs_prefix == "ExtraHop.Detections" @pytest.mark.parametrize("on_cloud", [False, True]) def test_list_detections_command_when_description_has_metric_link(on_cloud, requests_mock): """Test case scenario for successful execution of detections-list command when description has metrics link. Given: - User has provided valid credentials. When: - detections_list_command is called. Then: - Ensure human-readable output is correct. - Ensure outputs prefix is correct. """ requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"}) client = init_mock_client(requests_mock, on_cloud) args = { "limit": "1", "filter": """{ \"categories\": [\"sec.attack\"], \"risk_score_min\": 30 }""", "from": "1573500360001", "offset": "2", "sort": "end_time asc,id desc", "until": "1673569370001", } response = load_mock_response("list_detections_with_description_url.json") expected_hr = load_file("list_detections_with_description_url.md") requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=response) results = ExtraHop_v2.detections_list_command(client, args) assert results.readable_output == expected_hr assert results.outputs_prefix == "ExtraHop.Detections" @pytest.mark.parametrize("on_cloud", [False, True]) def test_list_detections_command_when_description_has_complete_metric_link(on_cloud, requests_mock): """Test case scenario for successful execution of detections-list command when description has metrics link which has only base url missing. Given: - User has provided valid credentials. When: - detections_list_command is called. Then: - Ensure human-readable output is correct. - Ensure outputs prefix is correct. """ requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"}) client = init_mock_client(requests_mock, on_cloud) args = { "limit": "1", "filter": """{ \"categories\": [\"sec.attack\"], \"risk_score_min\": 30 }""", "from": "1573500360001", "offset": "2", "sort": "end_time asc,id desc", "until": "1673569370001", } response = load_mock_response("list_detections_with_complete_description_url.json") expected_hr = load_file("list_detections_with_complete_description_url.md") requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=response) results = ExtraHop_v2.detections_list_command(client, args) assert results.readable_output == expected_hr assert results.outputs_prefix == "ExtraHop.Detections" def test_list_detections_command_using_advanced_filter(requests_mock): """Test case scenario for successful execution of detections list using advanced_filter argument. Given: - User has provided valid credentials. When: - detections_list_command is called. Then: - Ensure human-readable output is correct. - Ensure outputs prefix is correct. """ requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"}) client = init_mock_client(requests_mock, on_cloud=False) args = {} advanced_filter = """{\"filter\": {\"categories\": [\"sec.attack\"],\"risk_score_min\": 51}, \"limit\": 1,\"offset\": 0, \"sort\": [ { \"direction\": \"desc\", \"field\": \"end_time\" } ] }""" response = load_mock_response(LIST_DETECTIONS_SUCCESS) expected_hr = load_file("list_detections_success_hr.md") requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=response) results = ExtraHop_v2.detections_list_command(client, args, json.loads(advanced_filter)) assert results.readable_output == expected_hr def test_list_detections_command_using_advanced_filter_invalid_arg(requests_mock): """Test case scenario for execution of detections list using advanced_filter argument. Given: - User has provided invalid arguments in body. When: - detections_list_command is called. Then: - Ensure appropriate error is raised. """ requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"}) client = init_mock_client(requests_mock, on_cloud=False) args = {} advanced_filter = """{\"filter\": {\"categories\": [\"sec.attack\"],\"risk_score_min\": 51}, \"limit\": 1,\"offset\": 0,\"invalid_arg\": 0, \"sort\": [ { \"direction\": \"desc\", \"field\": \"end_time\" } ] }""" response = load_mock_response(LIST_DETECTIONS_SUCCESS) requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=response) with pytest.raises(Exception) as error: ExtraHop_v2.detections_list_command(client, args, advanced_filter=json.loads(advanced_filter)) assert ( str(error.value) == "invalid_arg is an invalid value for key. Possible values are: ['filter', 'limit', " "'offset', 'from', 'until', 'sort', 'mod_time']" ) def test_list_detections_command_error_code_400(requests_mock): """Test case scenario for failure detections list when status code is 400. Given: - User has provided invalid json input. When: - detections_list_command is called. Then: - Ensure error is raised with error code. """ requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"}) client = init_mock_client(requests_mock, on_cloud=False) args = { "limit": "2", } response = { "type": "invalid_request", "detail": "The JSON payload specified in the request is invalid.", } requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=response, status_code=400) with pytest.raises(Exception) as error: ExtraHop_v2.detections_list_command(client, args) assert ( str(error.value) == 'Error in API call [400] - None\n{"type": "invalid_request", "detail": "The JSON ' 'payload specified in the request is invalid."}' ) def test_list_detections_command_error_code_502(requests_mock): """Test case scenario for failure detections list when status code is 502. Given: - User has provided invalid arguments. When: - detections_list_command is called. Then: - Ensure error is raised with error code. """ requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"}) client = init_mock_client(requests_mock, on_cloud=False) args = {} requests_mock.post(f"{BASE_URL}/api/v1/detections/search", status_code=502) with pytest.raises(Exception) as error: ExtraHop_v2.detections_list_command(client, args) assert str(error.value) == "Error in API call [502] - None\n" # - Bad Gateway def test_list_detections_command_error_code_401(requests_mock): """Test case scenario for failure detections list when status code is 401. Given: - User has provided invalid credentials. When: - detections_list_command is called. Then: - Ensure error is raised with error code. """ requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"}) client = init_mock_client(requests_mock, on_cloud=False) args = {"limit": "2"} response = {"error_message": "API Key is invalid"} requests_mock.post(f"{BASE_URL}/api/v1/detections/search", status_code=401, json=response) with pytest.raises(Exception) as error: ExtraHop_v2.detections_list_command(client, args) assert str(error.value) == 'Error in API call [401] - None\n{"error_message": "API Key is invalid"}' @pytest.mark.parametrize( "detection_status, close_reason, incident_owner, expected", [ ( "2", "Resolved", "abc", { "ticket_id": "incident_id", "status": "closed", "assignee": "abc", "resolution": "action_taken", }, ), ( "2", "Duplicate", None, { "ticket_id": "incident_id", "status": "closed", "assignee": None, "resolution": "no_action_taken", }, ), ( "1", "Duplicate", None, {"ticket_id": "incident_id", "status": "in_progress", "assignee": None}, ), ( "2", "Not resolved", None, {"ticket_id": "incident_id", "status": "closed", "assignee": None}, ), ], ) def test_ticket_track_command_successful_execution( mocker, detection_status, close_reason, incident_owner, expected, requests_mock ) -> None: """ Test case scenario for successful execution of ticket-track command. Given: - User has provided valid argument values. When: - ticket_track_command is called. Then: - Ensure outputs_prefix is correct. - Ensure readable_output is correct. - Ensure command output is correct. - Ensure patch_detections method is called with expected arguments. """ args = { "incident_id": "incident_id", "detection_id": "detection_id", "incident_status": detection_status, "incident_close_reason": close_reason, "incident_owner": incident_owner, } client = init_mock_client(requests_mock, on_cloud=False) mocker.patch("ExtraHop_v2.ExtraHopClient.patch_detections") result = ExtraHop_v2.ticket_track_command(client, args) assert result.outputs_prefix == "ExtraHop" assert result.readable_output == "Successfully linked detection(detection_id) with incident(incident_id)" assert result.outputs == {"TicketId": "incident_id"} ExtraHop_v2.ExtraHopClient.patch_detections.assert_called_once_with("detection_id", expected) def test_validate_ticket_track_arguments_failed_execution(): """ Test case for ticket-track command that raise error for invalid argument. Given: - User has provided invalid incident_status. When: - validate_ticket_track_arguments is called. Then: - Ensure error is raised with extecpted error message. """ # Verify invalid value with pytest.raises(ExtraHop_v2.InvalidValueError) as err: ExtraHop_v2.validate_ticket_track_arguments("4") assert str(err.value) == "4 is an invalid value for incident_status. Possible values are: ['0', '1', '2', '3']" def test_validate_ticket_track_arguments_successful_execution(): """ Test case scenarios for successful execution of ticket-track command. Given: - User has provided valid incident_status. When: - validate_ticket_track_arguments is called. Then: - Ensure error is not raised for valid argument. """ # Verify valid value assert not ExtraHop_v2.validate_ticket_track_arguments("3") @pytest.mark.parametrize( "add_arg, remove_arg, expected", [ [ "id1, id2", "", "Successfully added new devices(id1, id2) in the watchlist \n", ], ["", "id1, id2", "Successfully removed devices(id1, id2) from the watchlist"], [ "id1, id2", "id3", "Successfully added new devices(id1, id2) in the watchlist \nSuccessfully removed devices(id3) from the watchlist", ], ], ) def test_watch_list_edit_command_successful_execution(requests_mock, mocker, add_arg, remove_arg, expected): """ Test case scenario for successful execution of watchlist-edit command. Given: - User has provided different arguments for watchlist-edit command. When: - watchlist-edit is called. Then: - Ensure readable_output is correct. """ args = {"add": add_arg, "remove": remove_arg} client = init_mock_client(requests_mock, on_cloud=False) requests_mock.post(f"{BASE_URL}/api/v1/watchlist/devices") mocker.patch("ExtraHop_v2.get_devices_by_ip_or_id", return_value=[]) actual = ExtraHop_v2.watchlist_edit_command(client, args) assert actual.readable_output == expected def test_packets_search_invalid_output(requests_mock): """ Test case scenario for invalid output in packets-search command. Given: User has provided valid credentials but invalid arguments. When: extrahop-packets-search command is called. Then: Raise error with right error message. """ args_copy = copy(get_packets_search_args()) args_copy["output"] = "gzip" with pytest.raises(ExtraHop_v2.InvalidValueError) as e: ExtraHop_v2.packets_search_command(init_mock_client(requests_mock, False), args_copy) assert str(e.value) == "gzip is an invalid value for output. Possible values are: ['pcap', 'keylog_txt', 'zip']" def test_packets_search_204_status_code(requests_mock): """ Test case scenario for packets-search command when no packets are returned from the API. Given: User has provided valid credentials. When: extrahop-packets-search command is called. Then: Return message about packets not found. """ requests_mock.get(BASE_URL + PACKETS_SEARCH_URL, status_code=204) response = ExtraHop_v2.packets_search_command(init_mock_client(requests_mock, False), get_packets_search_args()) assert response == "Search matched no packets." def test_packets_search_invalid_filename_headers(requests_mock): """ Test case scenario to raise error when unexpected filename format found in the response headers for packet-search command. Given: User has provided valid credentials and packet are found in the API. When: extrahop-packets-search command is called. Then: Return right error message. """ requests_mock.get( BASE_URL + PACKETS_SEARCH_URL, headers={"content-disposition": "file=packets.zip"}, content="this is dummy packets".encode("ascii"), ) with pytest.raises(ExtraHop_v2.DemistoException) as e: ExtraHop_v2.packets_search_command(init_mock_client(requests_mock, False), get_packets_search_args()) assert str(e.value) == "Error filename could not be found in response header." def test_packets_search_file_data(requests_mock): """ Test case scenario for packets-search command when packets received from the API. Given: User has provided valid credentials. When: extrahop-packets-search command is called. Then: Return file response. """ requests_mock.get( BASE_URL + PACKETS_SEARCH_URL, headers={"content-disposition": "filename=packets.zip"}, content="this is dummy packets".encode("ascii"), ) response = ExtraHop_v2.packets_search_command(init_mock_client(requests_mock, False), get_packets_search_args()) assert response.get("File") == "packets.zip" def test_devices_tag_invalid_args(requests_mock): """ Test case scenario to verify devices-tag command when "add" and "remove" tags not found in the args. Given: User has provided valid credentials. When: extrahop-devices-tag command is called. Then: Raise error with valid error message. """ with pytest.raises(ExtraHop_v2.DemistoException) as e: ExtraHop_v2.devices_tag_command( init_mock_client(requests_mock, False), {"tag": "MyTag", "add": None, "remove": None}, ) assert str(e.value) == "No device id provided to add or remove arguments." @pytest.mark.parametrize( "add, remove, expected_data", [ ("1,2", "3,4", '{"assign": [1, 2], "unassign": [3, 4]}'), ("1,2", None, '{"assign": [1, 2]}'), (None, "1,2", '{"unassign": [1, 2]}'), ], ) def test_devices_tag(add, remove, expected_data, requests_mock, get_devices_by_ip_or_id_fixture): """Test case scenario to verify devices-tag command when valid arguments provided. Given: User has provided valid credentials, and "add" or "remove" is present in demisto args. When: extrahop-devices-tag command is called. Then: User is informed of tag-untag devices. """ args_copy = copy(get_device_tag_args()) args_copy["add"] = add args_copy["remove"] = remove requests_mock.get( BASE_URL + GET_ALL_TAGS_URL, json=[ {"id": "12", "name": "MyTag"}, {"id": "13", "name": "TestTag"}, {"id": "14", "name": "DummyTag"}, ], ) requests_mock.post(BASE_URL + TAG_DEVICE_URL) response = ExtraHop_v2.devices_tag_command(init_mock_client(requests_mock, False), args_copy) assert response == "Successfully tagged untagged the device/s." assert requests_mock.request_history[1].text == expected_data @pytest.mark.parametrize( "add, remove, expected_data", [ ("1,2", "3,4", '{"assign": [1, 2], "unassign": [3, 4]}'), ("1,2", None, '{"assign": [1, 2]}'), ], ) def test_devices_tag_create_new_tag(add, remove, expected_data, get_devices_by_ip_or_id_fixture, requests_mock): """ Test case scenario for creating new Tag with tag name if no tag is found with expected name from the tags list, and call "tag_untag_devices" for that tag. Given: User has provided valid credentials. When: extrahop-devices-tag command is called. Then: Verify valid tag id was present in the tag-untag URL with expected json body. """ args_copy = copy(get_device_tag_args()) args_copy["add"] = add args_copy["remove"] = remove requests_mock.get( BASE_URL + GET_ALL_TAGS_URL, json=[ {"id": "t1", "name": "NotMyTag"}, {"id": "t2", "name": "TestTag"}, {"id": "t3", "name": "DummyTag"}, ], ) requests_mock.post(BASE_URL + TAG_DEVICE_URL) requests_mock.post(BASE_URL + GET_ALL_TAGS_URL, headers={"location": "US/12"}) response = ExtraHop_v2.devices_tag_command(init_mock_client(requests_mock, False), args_copy) assert response == "Successfully tagged untagged the device/s." assert requests_mock.request_history[2].path == TAG_DEVICE_URL assert requests_mock.request_history[2].text == expected_data def test_devices_tag_nothing_to_remove(requests_mock, get_devices_by_ip_or_id_fixture): """ Test case scenario to raise error when expected Tag not found from Reveal(X) and "remove" is present and "add" is not present in demisto args. Given: User has provided valid credentials. When: extrahop-devices-tag command is called. Then: Raise error with informative error message. """ args_copy = copy(get_device_tag_args()) del args_copy["add"] requests_mock.get( BASE_URL + GET_ALL_TAGS_URL, json=[ {"id": "t1", "name": "NotMyTag"}, {"id": "t2", "name": "TestTag"}, {"id": "t3", "name": "DummyTag"}, ], ) requests_mock.post(BASE_URL + TAG_DEVICE_URL) with pytest.raises(ExtraHop_v2.DemistoException) as e: ExtraHop_v2.devices_tag_command(init_mock_client(requests_mock, False), args_copy) assert str(e.value) == "The tag MyTag does not exist, nothing to remove." @pytest.mark.parametrize( "devices_str, id_only, expected_data", [ ("1", True, [1]), ("1", False, [{"id": 1, "ip": "0.0.0.0"}]), ("0.0.0.0", True, [1]), ("0.0.0.0", False, [{"id": 1, "ip": "0.0.0.0"}]), ], ) def test_get_devices_by_ip_or_id(devices_str, id_only, expected_data, requests_mock, mocker): """ Test case scenario to return devices list by IP address or device ID. Given: User has provided valid credentials, "add" or "remove" is present in the demisto args, ip addresses/device ids are provided in the device list string and id_only is true/false. When: extrahop-devices-tag command is called. Then: Return list of ids if id_only flag is Tue else device info. """ requests_mock.get(BASE_URL + "/api/v1/devices/1", json={"id": 1, "ip": "0.0.0.0"}) mocker.patch("ExtraHop_v2.get_device_by_ip", return_value={"id": 1, "ip": "0.0.0.0"}) devices = ExtraHop_v2.get_devices_by_ip_or_id(init_mock_client(requests_mock, False), devices_str, id_only=id_only) assert devices == expected_data def test_get_devices_by_ip_or_id_invalid_ip_address(requests_mock): """ Test case scenario to raise error when invalid IP address found from the add/remove args. Given: User has provided valid credentials, "add" or "remove" is present in the demisto args and ip addresses are provided in the device list string. When: extrahop-devices-tag command is called. Then: Raise error with informative error message. """ with pytest.raises(ExtraHop_v2.DemistoException) as e: ExtraHop_v2.get_devices_by_ip_or_id(init_mock_client(requests_mock, False), "10.0.0.0.1") assert str(e.value) == "Error parsing IP Address 10.0.0.0.1" @pytest.mark.parametrize("location", [None, "US/NYC"]) def test_parse_location_header_invalid_location(location): """ Test case scenario to raise error when no location response found from headers of "client.create_new_tag". Given: User has provided valid credentials, expected tag is not found in the tag list, and "add" and "remove" is present in demisto args. When: extrahop-devices-tag command is called. Then: Raise error with informative error message. """ with pytest.raises(ExtraHop_v2.DemistoException) as e: ExtraHop_v2.parse_location_header(location) assert str(e.value) == "Error unable to parse ExtraHop API response location header." def test_parse_location_header(): """Test case scenario to parse location data from "client.create_new_tag" API response. Given: User has provided valid credentials, expected tag is not found in the tag list, and "add" and "remove" is present in demisto args. When: extrahop-devices-tag command is called. Then: Return tag id from the response. """ tag_id = ExtraHop_v2.parse_location_header("US/12") assert tag_id == "12" @pytest.mark.parametrize("on_cloud", [False, True]) def test_extrahop_devices_search_command_success(on_cloud, requests_mock): """ Test case scenario for successful execution of device search command. Given: - User has provided valid arguments. When: - devices_search_command is called. Then: - Ensure human-readable output is correct. - Ensure outputs prefix is correct. """ client = init_mock_client(requests_mock, on_cloud=on_cloud) args = {"limit": 2} expected_response = load_mock_response("devices_search_response_success.json") networks = load_mock_response("get_networks.json") expected_readable_output = load_file("devices_search_response_success.md") requests_mock.post(f"{BASE_URL}/api/v1/devices/search", json=expected_response, status_code=200) requests_mock.get(f"{BASE_URL}/api/v1/networks", json=networks, status_code=200) result = ExtraHop_v2.devices_search_command(client, args, False) assert result.outputs_prefix == EXTRAHOP_DEVICE assert result.readable_output == expected_readable_output def test_extrahop_devices_search_command_success_empty_response(requests_mock): """ Test case scenario for successful execution of device search command. Given: - User has provided valid arguments. When: - devices_search_command is called. Then: - Ensure human-readable output is correct. - Ensure outputs prefix is correct. """ client = init_mock_client(requests_mock, on_cloud=False) args = {"limit": 2} expected_response = [] networks = load_mock_response("get_networks.json") expected_readable_output = "No Devices found" requests_mock.post(f"{BASE_URL}/api/v1/devices/search", json=expected_response, status_code=200) requests_mock.get(f"{BASE_URL}/api/v1/networks", json=networks, status_code=200) result = ExtraHop_v2.devices_search_command(client, args, False) assert result.outputs_prefix == EXTRAHOP_DEVICE assert result.readable_output == expected_readable_output @pytest.mark.parametrize( "args, message", [ ( {"role": "dbserver"}, f"dbserver is an invalid value for role. Possible values are: {ExtraHop_v2.VALID_DEVICE_ROLES}", ), ( {"software": "MacOS"}, f"MacOS is an invalid value for software. Possible values are: {ExtraHop_v2.VALID_DEVICE_SOFTWARES}", ), ( {"vendor": "lenovo"}, f"lenovo is an invalid value for vendor. Possible values are: {ExtraHop_v2.VALID_DEVICE_VENDORS}", ), ( {"match_type": "nor"}, f"nor is an invalid value for match_type. Possible values are: {ExtraHop_v2.VALID_DEVICE_MATCH_TYPES}", ), ( {"operator": "=="}, f"== is an invalid value for operator. Possible values are: {ExtraHop_v2.VALID_DEVICE_OPERATORS}", ), ( {"activity": "abc_client"}, f"abc_client is an invalid value for activity. Possible values are: {ExtraHop_v2.VALID_DEVICE_ACTIVITIES}", ), ], ) def test_extrahop_devices_search_command_with_invalid_arguments(args, message, requests_mock): """ Test case scenario for invalid arguments while execution of device search command. Given: - User has provided invalid arguments. When: - devices_search_command is called. Then: - Ensure appropriate error is raised. """ client = init_mock_client(requests_mock, on_cloud=False) with pytest.raises(ExtraHop_v2.InvalidValueError) as error: ExtraHop_v2.devices_search_command(client, args, False) assert str(error.value) == message def test_extrahop_devices_search_command_failure_400(requests_mock): """ Test case scenario for execution of device search command when invalid id is passed. Given: - User has provided invalid arguments. When: - devices_search_command is called. Then: - Ensure appropriate error is generated. """ client = init_mock_client(requests_mock, on_cloud=False) args = {"limit": "2"} expected_response = { "type": "invalid_request", "detail": "'limit' must be one of the following types: int", } requests_mock.post(f"{BASE_URL}/api/v1/devices/search", json=expected_response, status_code=400) with pytest.raises(Exception) as error: ExtraHop_v2.devices_search_command(client, args, False) assert ( str(error.value) == 'Error in API call [400] - None\n{"type": "invalid_request", "detail": "\'limit\' must be one' ' of the following types: int"}' ) def test_extrahop_devices_search_command_failure_401(requests_mock): """ Test case scenario for execution of device search command when authentication is invalid. Given: - User has provided valid arguments. When: - devices_search_command is called. Then: - Ensure appropriate error is generated. """ client = init_mock_client(requests_mock, on_cloud=False) args = {"limit": 2} expected_response = {"error_message": INVALID_AUTH_HEADER} requests_mock.post(f"{BASE_URL}/api/v1/devices/search", json=expected_response, status_code=401) with pytest.raises(Exception) as error: ExtraHop_v2.devices_search_command(client, args, False) assert str(error.value) == 'Error in API call [401] - None\n{"error_message": "' + INVALID_AUTH_HEADER + '"}' @pytest.mark.parametrize( "args, message", [ ({"ip_or_id": "10:1:1"}, "Error parsing IP Address 10:1:1"), ], ) def test_extrahop_protocols_get_invalid_ip(args, message, requests_mock): """ Test case scenario for passing invalid ip while execution of protocol get command. Given: - User has provided invalid ip address. When: - protocols_get_command is called. Then: - Ensure appropriate error is raised. """ client = init_mock_client(requests_mock, on_cloud=False) with pytest.raises(Exception) as error: ExtraHop_v2.protocols_get_command(client, args, False) assert str(error.value) == message def test_extrahop_protocols_get_failure_404(requests_mock): """ Test case scenario for execution of protocol get command when id is not found. Given: - User has provided valid arguments. When: - protocols_get_command is called. Then: - Ensure appropriate error is generated. """ client = init_mock_client(requests_mock, on_cloud=False) args = {"ip_or_id": "23133113"} expected_response = '{"error_message": "The specified object was not found."}' requests_mock.get( f"{BASE_URL}/api/v1/devices/{args['ip_or_id']}", json=expected_response, status_code=404, ) with pytest.raises(Exception) as error: ExtraHop_v2.protocols_get_command(client, args, False) assert ( str(error.value) == 'Error in API call [404] - None\n"{\\"error_message\\": \\"The specified object was ' 'not found.\\"}"' ) def test_extrahop_protocols_get_failure_401(requests_mock): """ Test case scenario for execution of protocol get command when authentication is invalid. Given: - User has provided valid arguments. When: - protocols_get_command is called. Then: - Ensure appropriate error is generated. """ client = init_mock_client(requests_mock, on_cloud=False) args = {"ip_or_id": "2"} expected_response = {"error_message": INVALID_AUTH_HEADER} requests_mock.get( f"{BASE_URL}/api/v1/devices/{args['ip_or_id']}", json=expected_response, status_code=401, ) with pytest.raises(Exception) as error: ExtraHop_v2.protocols_get_command(client, args, False) assert str(error.value) == 'Error in API call [401] - None\n{"error_message": "' + INVALID_AUTH_HEADER + '"}' @pytest.mark.parametrize("on_cloud", [False, True]) def test_extrahop_protocols_get_success_get_device_by_id(on_cloud, requests_mock): """ Test case scenario for successful execution of protocol get command when id is passed. Given: - User has provided valid arguments. When: - protocols_get_command is called. Then: - Ensure human-readable output is correct. - Ensure outputs prefix is correct. """ client = init_mock_client(requests_mock, on_cloud=on_cloud) args = {"ip_or_id": "3564"} expected_response = load_mock_response("protocols_get_success_get_device_by_id.json") expected_readable_output = load_file("get_protocol_readable_output.md") expected_activity_map = load_mock_response(GET_PROTOCOL_FILE) expected_networks = load_mock_response(GET_PROTOCOL_NETWORKS_FILE) requests_mock.get( f"{BASE_URL}/api/v1/devices/{args['ip_or_id']}", json=expected_response, status_code=200, ) requests_mock.post( f"{BASE_URL}/api/v1/activitymaps/query", json=expected_activity_map, status_code=200, ) requests_mock.get(f"{BASE_URL}/api/v1/networks", json=expected_networks, status_code=200) result = ExtraHop_v2.protocols_get_command(client, args, False) assert result.outputs_prefix == EXTRAHOP_DEVICE assert result.readable_output == expected_readable_output def test_extrahop_protocols_get_success_get_device_by_ip(requests_mock): """ Test case scenario for successful execution of protocol get command when valid ip is passed. Given: - User has provided valid arguments. When: - protocols_get_command is called. Then: - Ensure human-readable output is correct. - Ensure outputs prefix is correct. """ client = init_mock_client(requests_mock, on_cloud=False) args = {"ip_or_id": "0.0.0.0"} expected_response = load_mock_response("protocols_get_success_get_device_by_ip.json") expected_readable_output = load_file("get_protocol_readable_output.md") expected_activity_map = load_mock_response(GET_PROTOCOL_FILE) expected_networks = load_mock_response(GET_PROTOCOL_NETWORKS_FILE) requests_mock.post(f"{BASE_URL}/api/v1/devices/search", json=expected_response, status_code=200) requests_mock.post( f"{BASE_URL}/api/v1/activitymaps/query", json=expected_activity_map, status_code=200, ) requests_mock.get(f"{BASE_URL}/api/v1/networks", json=expected_networks, status_code=200) result = ExtraHop_v2.protocols_get_command(client, args, False) assert result.outputs_prefix == EXTRAHOP_DEVICE assert result.readable_output == expected_readable_output def test_extrahop_protocols_get_ip_not_present_in_extrahop(requests_mock): """Test case scenario for passing valid ip but is not present in extrahop while execution of protocol get command. Given: - User has provided valid arguments. When: - protocols_get_command is called. Then: - Ensure appropriate error is raised. """ client = init_mock_client(requests_mock, on_cloud=False) args = {"ip_or_id": "0.0.0.0"} expected_error_message = f"Error the IP Address {args['ip_or_id']} was not found in ExtraHop." # noqa: E713 expected_response = [] requests_mock.post(f"{BASE_URL}/api/v1/devices/search", json=expected_response, status_code=200) with pytest.raises(DemistoException) as error: ExtraHop_v2.protocols_get_command(client, args, False) assert str(error.value) == expected_error_message def test_extrahop_protocols_get_device_by_id_discovery(requests_mock): """ Test case scenario for execution of protocol get command when the device has analysis field as discovery. Given: - User has provided valid arguments. When: - protocols_get_command is called. Then: - Ensure human-readable output is correct. - Ensure outputs prefix is correct. """ client = init_mock_client(requests_mock, on_cloud=False) args = {"ip_or_id": "567"} expected_response = load_mock_response("protocols_get_success_get_device_by_id_discovery.json") expected_activity_map = load_mock_response(GET_PROTOCOL_FILE) expected_networks = load_mock_response(GET_PROTOCOL_NETWORKS_FILE) requests_mock.get( f"{BASE_URL}/api/v1/devices/{args['ip_or_id']}", json=expected_response, status_code=200, ) requests_mock.post( f"{BASE_URL}/api/v1/activitymaps/query", json=expected_activity_map, status_code=200, ) requests_mock.get(f"{BASE_URL}/api/v1/networks", json=expected_networks, status_code=200) result = ExtraHop_v2.protocols_get_command(client, args, False) assert result.outputs_prefix == EXTRAHOP_DEVICE assert result.readable_output == "No Protocol activity found" @pytest.mark.parametrize("on_cloud", [False, True]) def test_alerts_rules_get_command_success(on_cloud, requests_mock): """Test case scenario for successful execution of alert rules get command. Given: - User has provided valid credentials. When: - alerts_rules_get_command is called. Then: - Ensure human-readable output is correct. - Ensure outputs prefix is correct. """ client = init_mock_client(requests_mock, on_cloud=on_cloud) expected_response = load_mock_response("alerts_get.json") expected_readable_output = load_file("alerts_get.md") requests_mock.get(f"{BASE_URL}/api/v1/alerts", json=expected_response) result = ExtraHop_v2.alerts_rules_get_command(client) assert result.outputs_prefix == "ExtraHop.Alert" assert result.readable_output == expected_readable_output def test_alerts_rules_get_command_success_no_alerts(requests_mock): """Test case scenario for successful execution of alert rules get command. Given: - User has provided valid credentials. When: - alerts_rules_get_command is called. Then: - Ensure human-readable output is correct. - Ensure outputs prefix is correct. """ client = init_mock_client(requests_mock, on_cloud=False) expected_response = [] expected_readable_output = "No Alerts were found." requests_mock.get(f"{BASE_URL}/api/v1/alerts", json=expected_response, status_code=200) result = ExtraHop_v2.alerts_rules_get_command(client) assert result.outputs_prefix == "ExtraHop.Alert" assert result.readable_output == expected_readable_output def test_alerts_rules_get_command_failure_401(requests_mock): """Test case scenario for execution of alert rules get command when authentication is invalid. Given: - User has provided invalid credentials. When: - alerts_rules_get_command is called. Then: - Ensure appropriate error is generated. """ client = init_mock_client(requests_mock, on_cloud=False) expected_response = {"error_message": INVALID_AUTH_HEADER} requests_mock.get(f"{BASE_URL}/api/v1/alerts", json=expected_response, status_code=401) with pytest.raises(Exception) as error: ExtraHop_v2.alerts_rules_get_command(client) assert str(error.value) == 'Error in API call [401] - None\n{"error_message": "' + INVALID_AUTH_HEADER + '"}' def test_activity_map_get_command_failure_401(requests_mock): """Test case scenario for execution of activity map get command when authentication is invalid. Given: - User has provided invalid credentials. When: - activity_map_get_command is called. Then: - Ensure appropriate error is generated. """ client = init_mock_client(requests_mock, on_cloud=False) args = {"ip_or_id": "3564"} expected_response = {"error_message": INVALID_AUTH_HEADER} requests_mock.get( f"{BASE_URL}/api/v1/devices/{args['ip_or_id']}", json=expected_response, status_code=401, ) with pytest.raises(Exception) as error: ExtraHop_v2.activity_map_get_command(client, args, False) assert str(error.value) == 'Error in API call [401] - None\n{"error_message": "' + INVALID_AUTH_HEADER + '"}' def test_activity_map_get_command_failure_404(requests_mock): """Test case scenario for execution of activity map get command when id is not found. Given: - User has provided valid arguments. When: - activity_map_get_command is called. Then: - Ensure appropriate error is generated. """ client = init_mock_client(requests_mock, on_cloud=False) args = {"ip_or_id": "23133113"} expected_response = '{"error_message": "The specified object was not found."}' requests_mock.get( f"{BASE_URL}/api/v1/devices/{args['ip_or_id']}", json=expected_response, status_code=404, ) with pytest.raises(Exception) as error: ExtraHop_v2.activity_map_get_command(client, args, False) assert ( str(error.value) == 'Error in API call [404] - None\n"{\\"error_message\\": \\"The specified object was ' 'not found.\\"}"' ) @pytest.mark.parametrize( "args, message", [ ( {"ip_or_id": "10.1.2"}, "'10.1.2' does not appear to be an IPv4 or IPv6 address", ), ( {"ip_or_id": "0.0.0.0", "time_interval": "1 hour"}, f"1 hour is an invalid value for time_interval. Possible values are: {ExtraHop_v2.VALID_TIME_INTERVALS}", ), ( {"ip_or_id": "0.0.0.0", "from_time": "30m"}, 'Invalid number: "from_time"="30m"', ), ( {"ip_or_id": "0.0.0.0", "until_time": "50m"}, 'Invalid number: "until_time"="50m"', ), ( {"ip_or_id": "0.0.0.0", "peer_role": "temp"}, f"temp is an invalid value for peer_role. Possible values are: {ExtraHop_v2.VALID_PEER_ROLES}", ), ( {"ip_or_id": "0.0.0.0", "protocol": "ICAMP"}, f"ICAMP is an invalid value for protocol. Possible values are: {ExtraHop_v2.VALID_PROTOCOLS}", ), ], ) def test_activity_map_get_command_invalid_arguments(args, message, requests_mock): """Test case scenario for execution of activity map get command when invalid arguments are passed. Given: - User has provided invalid arguments. When: - activity_map_get_command is called. Then: - Ensure appropriate error is generated. """ client = init_mock_client(requests_mock, on_cloud=False) with pytest.raises(Exception) as error: ExtraHop_v2.activity_map_get_command(client, args, False) assert str(error.value) == message @pytest.mark.parametrize( "args, message", [ ( {"ip_or_id": "3564", "from_time": "30"}, "When using a fixed time range both from_time and until_time timestamps need to be provided.", ), ( {"ip_or_id": "3564", "until_time": "30"}, "When using a fixed time range both from_time and until_time timestamps need to be provided.", ), ], ) def test_activity_map_get_command_failure_when_either_from_time_or_until_time_is_given(args, message, requests_mock): """Test case scenario for execution of activity map get command when either from_time or until_time is given. Given: - User has provided valid arguments. When: - activity_map_get_command is called. Then: - Ensure appropriate error is generated. """ client = init_mock_client(requests_mock, on_cloud=False) expected_response = load_mock_response("activity_map_devices_by_id.json") requests_mock.get( f"{BASE_URL}/api/v1/devices/{args['ip_or_id']}", json=expected_response, status_code=200, ) with pytest.raises(Exception) as error: ExtraHop_v2.activity_map_get_command(client, args, False) assert str(error.value) == message @pytest.mark.parametrize("on_cloud", [False, True]) def test_activity_map_get_command_success_id(on_cloud, requests_mock): """Test case scenario for successful execution of activity map get command when valid id is passed. Given: - User has provided valid arguments. When: - activity_map_get_command is called. Then: - Ensure human-readable output is correct. - Ensure outputs prefix is correct. """ client = init_mock_client(requests_mock, on_cloud=on_cloud) args = {"ip_or_id": "3564"} expected_response = load_mock_response("activity_map_devices_by_id.json") expected_network = load_mock_response("activity_get_networks.json") expected_readable_output = load_file("activity_map_get_readable_output.md") requests_mock.get( f"{BASE_URL}/api/v1/devices/{args['ip_or_id']}", json=expected_response, status_code=200, ) requests_mock.get(f"{BASE_URL}/api/v1/networks", json=expected_network, status_code=200) result = ExtraHop_v2.activity_map_get_command(client, args, False) assert result.outputs_prefix == "ExtraHop.ActivityMap" assert result.readable_output == str(expected_readable_output) def test_activity_map_get_command_success_ip(requests_mock): """Test case scenario for successful execution of activity map get command when valid ip is passed. Given: - User has provided valid arguments. When: - activity_map_get_command is called. Then: - Ensure human-readable output is correct. - Ensure outputs prefix is correct. """ client = init_mock_client(requests_mock, on_cloud=False) args = {"ip_or_id": "0.0.0.0"} expected_response = load_mock_response("activity_map_devices_by_ip.json") expected_network = load_mock_response("activity_get_networks.json") expected_readable_output = load_file("activity_map_get_readable_output.md") requests_mock.post(f"{BASE_URL}/api/v1/devices/search", json=expected_response, status_code=200) requests_mock.get(f"{BASE_URL}/api/v1/networks", json=expected_network, status_code=200) result = ExtraHop_v2.activity_map_get_command(client, args, False) assert result.outputs_prefix == "ExtraHop.ActivityMap" assert result.readable_output == str(expected_readable_output) def setup_peers_get_command_by_id(is_discovery: bool, is_check_for_empty: bool, requests_mock) -> ExtraHop_v2.CommandResults: """Mock the result of extrahop-peers-get command. Args: is_discovery: Set true for mock response has analysis set to discovery. is_check_for_empty: Set true for test empty device output. requests_mock: This parameter is used for creating mock_request. Returns: Command result object which have output for extrahop-peers-get command. """ mock_response_uuid = load_mock_response("appliance_uuids_get_success.json") mock_response_for_activity_map = load_mock_response("get_peers_activity_maps.json") mock_response_for_get_device_id = load_mock_response("get_peer_device_by_id.json") if is_discovery: mock_response_for_get_device_id["analysis"] = "discovery" if is_check_for_empty: mock_response_for_activity_map["edges"] = [] requests_mock.post( f"{BASE_URL}/api/v1/activitymaps/query", status_code=200, json=mock_response_for_activity_map, ) requests_mock.get(f"{BASE_URL}/api/v1/networks", status_code=200, json=mock_response_uuid) requests_mock.get( f"{BASE_URL}/api/v1/devices/206", status_code=200, json=mock_response_for_get_device_id, ) mock_client = init_mock_client(requests_mock, on_cloud=False) args = { "ip_or_id": 206, "query_from": "-60m", "query_until": "0", "protocol": "any", "peer_role": "any", } result = ExtraHop_v2.peers_get_command(mock_client, args, False) return result def test_peers_get_command_on_prem_by_ip_success(requests_mock) -> None: """Test case scenario to validate peers_get command with valid ip value. Given: - User has provided valid ip and arguments. When: - Peers-get-command is called. Then: - Ensure number of arguments are correct. - Ensure command returns proper output. """ mock_response_devices = load_mock_response("get_peers_device_search.json") mock_response_uuid = load_mock_response("appliance_uuids_get_success.json") mock_response_for_activity_map = load_mock_response("get_peers_activity_maps.json") mock_response_for_get_device_id = load_mock_response("get_peer_device_by_id.json") requests_mock.post(f"{BASE_URL}/api/v1/devices/search", status_code=200, json=mock_response_devices) requests_mock.post( f"{BASE_URL}/api/v1/activitymaps/query", status_code=200, json=mock_response_for_activity_map, ) requests_mock.get(f"{BASE_URL}/api/v1/networks", status_code=200, json=mock_response_uuid) requests_mock.get( f"{BASE_URL}/api/v1/devices/3564", status_code=200, json=mock_response_for_get_device_id, ) mock_client = init_mock_client(requests_mock, on_cloud=False) args = { "ip_or_id": "0.0.0.0", "query_from": "-60m", "query_until": "0", "protocol": "any", } result = ExtraHop_v2.peers_get_command(mock_client, args, False) peer_get_success_result = load_mock_response("peer_get_command_on_prem_ip_success.json") peer_get_hr_result = load_file("peer_get_command_on_prem_ip_success.md") assert result.outputs == ExtraHop_v2.remove_empty_elements_from_response(peer_get_success_result) assert result.readable_output == peer_get_hr_result assert result.outputs_key_field == "id" assert result.raw_response == peer_get_success_result assert result.outputs_prefix == EXTRAHOP_DEVICE def test_peers_get_command_on_prem_by_id_success(requests_mock) -> None: """Test case scenario to validate peers_get command with valid id value. Given: - User has provided valid ip and arguments. When: - Peers-get-command is called. Then: - Ensure number of arguments are correct. - Ensure command returns proper output. """ result = setup_peers_get_command_by_id(False, False, requests_mock) peer_get_success_result = load_mock_response("peer_get_command_on_prem_id_success.json") peer_get_hr_result = load_file("peer_get_command_on_prem_id_success.md") assert result.outputs == ExtraHop_v2.remove_empty_elements_from_response(peer_get_success_result) assert result.readable_output == peer_get_hr_result assert result.outputs_key_field == "id" assert result.raw_response == peer_get_success_result assert result.outputs_prefix == EXTRAHOP_DEVICE def test_peers_get_command_with_discovery_success(requests_mock) -> None: """Test case scenario command with mock response has set analysis field set to discovery runs successfully. Given: - User has provided valid id and arguments and response has analysis field set to discovery. When: - Peers-get-command is called. Then: - Ensure number of arguments are correct. - Ensure command returns proper output. """ result = setup_peers_get_command_by_id(True, False, requests_mock) peer_discovery_success_result = load_mock_response("get_peer_discovery_output.json") peer_discovery_hr = load_file("get_peer_discovery_output.md") assert peer_discovery_success_result == result.raw_response assert peer_discovery_hr == result.readable_output @pytest.mark.parametrize( "args, error_message", [ ( { "ip_or_id": 206, "query_from": "-60m", "query_until": "0", "protocol": "asdf", "peer_role": "any", }, f"asdf is an invalid value for protocol. Possible values are: {ExtraHop_v2.VALID_PROTOCOLS}", ), ( { "ip_or_id": 206, "query_from": "-60m", "query_until": "0", "protocol": "any", "peer_role": "asdf", }, f"asdf is an invalid value for peer_role. Possible values are: {ExtraHop_v2.VALID_PEER_ROLES}", ), ], ) def test_validate_arguments_for_get_peer_command_failure(args: dict, error_message: str, requests_mock) -> None: """Test case scenario for invalid arguments while execution of peers-get-command. Given: - User has provided invalid arguments. When: - Peers-get-command is called. Then: - Ensure appropriate error raised. """ mock_client = init_mock_client(on_cloud=False, requests_mock=requests_mock) with pytest.raises(ExtraHop_v2.InvalidValueError) as error: _ = ExtraHop_v2.peers_get_command(mock_client, args, False) assert error_message == str(error.value) def test_validate_ip_for_get_peer_command_failure(requests_mock) -> None: """Test case scenario for invalid ip value while execution of peers-get-command. Given: - User has provided invalid ip. When: - Peers-get-command is called. Then: - Ensure appropriate error raised. """ args = {"ip_or_id": "1:1:1"} mock_client = init_mock_client(on_cloud=False, requests_mock=requests_mock) with pytest.raises(ExtraHop_v2.DemistoException) as error: _ = ExtraHop_v2.peers_get_command(mock_client, args, False) assert str(error.value) == "Error parsing IP Address 1:1:1" def test_empty_response_of_device_failure_for_peers_get_command(requests_mock) -> None: """Test case scenario for validating provided ip is not found. Given: - User has provided valid arguments. When: - Peers-get-command is called but provided ip can't find on ExtraHop instance. Then: - Ensure appropriate error raised. """ requests_mock.post(f"{BASE_URL}/api/v1/devices/search", status_code=404, json=[]) mock_client = init_mock_client(on_cloud=False, requests_mock=requests_mock) args = { "ip_or_id": "0.0.0.0", "query_from": "-60m", "query_until": "0", "protocol": "any", } with pytest.raises(ExtraHop_v2.DemistoException) as error: _ = ExtraHop_v2.peers_get_command(mock_client, args, False) assert str(error.value) == "Error in API call [404] - None\n[]" def test_for_prepare_device_get_output_success(requests_mock) -> None: """Test case scenario for validating empty response. Given: - User has provided valid ip and arguments. When: - Peers-get-command is called but ExtraHop returns empty response. Then: - Ensure command returns proper output. """ result = setup_peers_get_command_by_id(False, True, requests_mock) assert result.readable_output == "No Devices found" assert result.raw_response == [] def test_module_on_prem_success(requests_mock) -> None: """Test case scenario for test connection with on-prem instance. Given: - User has provided valid arguments. When: - Test_module is called. Then: - Ensure command returns proper output. """ mock_client = init_mock_client(on_cloud=False, requests_mock=requests_mock) success_response = load_mock_response("test_module_on_prem.json") requests_mock.get(f"{BASE_URL}/api/v1/extrahop", status_code=200, json=success_response) requests_mock.get( f"{BASE_URL}/api/v1/extrahop/version", status_code=200, json={"version": "9.9.9"}, ) result = ExtraHop_v2.test_module(mock_client) assert result == "ok" def test_module_on_cloud(requests_mock) -> None: """Test case scenario for test connection with on-cloud instance returns success. Given: - User has provided valid arguments. When: - Test_module is called. Then: - Ensure command returns proper output. """ mock_client = init_mock_client(on_cloud=False, requests_mock=requests_mock) success_response = load_mock_response("test_module_on_cloud.json") requests_mock.get(f"{BASE_URL}/api/v1/extrahop", status_code=200, json=success_response) requests_mock.get( f"{BASE_URL}/api/v1/extrahop/version", status_code=200, json={"version": "9.9.9"}, ) result = ExtraHop_v2.test_module(mock_client) assert result == "ok" def test_module_failure_on_prem_failure(requests_mock) -> None: """Unit test case scenario for test connection with on-prem instance for validating failure. Given: - User has provided invalid arguments. When: - Test_module is called. Then: - Ensure appropriate error raised. """ mock_client = init_mock_client(on_cloud=False, requests_mock=requests_mock) requests_mock.get(f"{BASE_URL}/api/v1/extrahop", status_code=401, json=[]) with pytest.raises(ValueError) as error: _ = ExtraHop_v2.test_module(mock_client) assert str(error.value) == "Error code 401: Invalid credentials provided." @pytest.mark.parametrize( "parameters, error_msg, last_run", [ ({"first_fetch": "++"}, '"++" is not a valid date', {}), ( {"advanced_filter": "{'risk_score_min':50}"}, "Invalid JSON string provided for advanced filter.", {}, ), ], ) def test_fetch_detection_when_invalid_arguments_provided(parameters, error_msg, last_run, requests_mock): """Test case scenario for execution of fetch_detections when invalid arguments are provided. Given: - Parameters for fetch_incident When: - Calling `fetch_incidents` function Then: - Returns a valid error message. """ client = init_mock_client(requests_mock, on_cloud=False) requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"}) with pytest.raises(ValueError) as err: ExtraHop_v2.fetch_incidents(client, parameters, last_run, False) assert str(err.value) == error_msg def test_fetch_detections_failure_when_firmware_version_is_outdated(requests_mock): """Test case scenario for execution of fetch_detections when ExtraHop firmware version is less than 9.3.0. Given: - Parameters for fetch_incident When: - Calling `fetch_incidents` function Then: - Returns a valid error message. """ client = init_mock_client(requests_mock, on_cloud=False) requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.1.2.1943"}) with pytest.raises(DemistoException) as err: ExtraHop_v2.fetch_incidents(client, {}, {}, False) assert ( str(err.value) == "This integration works with ExtraHop firmware version greater than or equal to 9.3.0 " "and current firmware version being used by you is 9.1.2" ) @pytest.mark.parametrize("advanced_filter", ["{}", '{"categories":["sec.attack"]}']) def test_fetch_detection_success_with_last_run(requests_mock, advanced_filter): """Test case scenario for execution of fetch_detections when last_run is present. Given: - command arguments for fetch_incident When: - Calling `fetch_incidents` function Then: - Returns a valid output """ incidents = load_mock_response("mock_incidents.json") requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"}) mock_response = load_mock_response("fetch_detections_success.json") requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=mock_response) mock_device_data = load_mock_response("mock_device_data.json") requests_mock.get(f"{BASE_URL}/api/v1/devices/1904", json=mock_device_data) mock_time = datetime.datetime.now() mock_time = int((mock_time.timestamp() + 10) * 1000) # adding 10 seconds to time and then convert to milliseconds client = init_mock_client(requests_mock, on_cloud=False) last_run = { "detection_start_time": 1676896891452, "offset": 0, "version_recheck_time": mock_time, } actual_incidents, next_run = ExtraHop_v2.fetch_incidents(client, {"advanced_filter": advanced_filter}, last_run, False) assert next_run == { "detection_start_time": 1673518450001, "offset": 0, "version_recheck_time": mock_time, "already_fetched": [997222], } assert actual_incidents[0]["name"] == incidents[0]["name"] assert actual_incidents[0]["occurred"] == incidents[0]["occurred"] assert actual_incidents[0]["rawJSON"] == json.dumps(incidents[0]["rawJSON"]) def test_fetch_detection_participants_is_empty(requests_mock): """Test case scenario for execution of fetch_detections when participant is empty list. Given: - command arguments for fetch_incident When: - Calling `fetch_incidents` function Then: - Returns a valid output """ incidents = load_mock_response("mock_incidents_no_participants.json") requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"}) mock_response = load_mock_response("fetch_detections_empty_participants.json") requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=mock_response) mock_time = datetime.datetime.now() mock_time = int((mock_time.timestamp() + 10) * 1000) # adding 10 seconds to time and then convert to milliseconds client = init_mock_client(requests_mock, on_cloud=False) last_run = { "detection_start_time": 1676896891452, "offset": 0, "version_recheck_time": mock_time, } actual_incidents, next_run = ExtraHop_v2.fetch_incidents(client, {}, last_run, False) assert next_run == { "detection_start_time": 1673518450001, "offset": 0, "version_recheck_time": mock_time, "already_fetched": [997222], } assert actual_incidents[0]["name"] == incidents[0]["name"] assert actual_incidents[0]["occurred"] == incidents[0]["occurred"] assert actual_incidents[0]["rawJSON"] == json.dumps(incidents[0]["rawJSON"]) @mock.patch("ExtraHop_v2.MAX_FETCH", 1) def test_fetch_detections_success_when_detections_equal_to_max_fetch(requests_mock): """Test case scenario for execution of fetch_detections when no of records are equal or greater than max_fetch. Given: - command arguments for fetch_incidents When: - Calling `fetch_incidents` function Then: - Returns a valid output """ incidents = load_mock_response("mock_incidents.json") requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"}) mock_response = load_mock_response("fetch_detections_success.json") requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=mock_response) mock_device_data = load_mock_response("mock_device_data.json") requests_mock.get(f"{BASE_URL}/api/v1/devices/1904", json=mock_device_data) client = init_mock_client(requests_mock, on_cloud=False) actual_incidents, next_run = ExtraHop_v2.fetch_incidents(client, {}, {}, False) assert next_run["offset"] == 1 assert actual_incidents[0]["name"] == incidents[0]["name"] assert actual_incidents[0]["occurred"] == incidents[0]["occurred"] assert actual_incidents[0]["rawJSON"] == json.dumps(incidents[0]["rawJSON"]) def test_fetch_incident_empty_response(requests_mock): """Test case scenario for execution of fetch_detections when no records are returned from API. Given: - command arguments for fetch_incidents When: - Calling `fetch_incidents` function Then: - Returns a valid output """ client = init_mock_client(requests_mock, on_cloud=False) last_run = {"update_or_mod_time": "update_time"} parameters = {"first_fetch": "1 Jan"} requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"}) requests_mock.post(f"{BASE_URL}/api/v1/detections/search", json=[], status_code=200) actual_incidents, next_run = ExtraHop_v2.fetch_incidents(client, parameters, last_run, False) assert actual_incidents == [] def test_test_module_failure_extrahop_version_is_outdated(requests_mock): """Test case scenario for execution of test_module when ExtraHop firmware version is outdated. Given: - command arguments for test_module When: - Calling `test_module` function Then: - Returns a valid error message. """ client = init_mock_client(requests_mock, on_cloud=False) requests_mock.get(f"{BASE_URL}/api/v1/extrahop", json={}) requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.1.2.1943"}) with pytest.raises(DemistoException) as err: ExtraHop_v2.test_module(client) assert ( str(err.value) == "This integration works with ExtraHop firmware version greater than or equal to 9.3.0 " "and current firmware version being used by you is 9.1.2" ) def test_test_module_failure(requests_mock): """Test case scenario for failure of test_module. Given: - command arguments for test_module When: - Calling `test_module` function Then: - Returns a valid error message. """ client = init_mock_client(requests_mock, on_cloud=False) requests_mock.get(f"{BASE_URL}/api/v1/extrahop", json={}) requests_mock.get(f"{BASE_URL}/api/v1/extrahop/version", json={"version": "9.3.0.1319"}) with pytest.raises(ValueError) as err: ExtraHop_v2.test_module(client) assert str(err.value) == "Failed to establish connection with provided credentials." def test_authenticate_uses_basic_auth(requests_mock) -> None: """Test that authenticate sends client credentials via HTTP Basic Auth, not in the request body. Given: - A cloud ExtraHop client with client_id and client_secret. When: - authenticate is called to obtain an access token. Then: - The POST to /oauth2/token uses HTTP Basic Auth with client_id and client_secret. - The request body does NOT contain client_id or client_secret. - The returned token matches the mocked access_token. """ auth_response = load_mock_response("auth_token.json") token_request = requests_mock.post("/oauth2/token", json=auth_response) client = ExtraHop_v2.ExtraHopClient( base_url=BASE_URL, api_key=API_KEY, client_id=CLIENT_ID, client_secret=CLIENT_SECRET, verify=False, use_proxy=False, ok_codes=(200, 201, 204), on_cloud=True, ) token, expires_in = client.authenticate(CLIENT_ID, CLIENT_SECRET) assert token == auth_response["access_token"] assert expires_in == auth_response["expires_in"] last_request = token_request.last_request assert last_request.headers.get("Authorization", "").startswith("Basic ") body = last_request.text assert "client_id" not in body assert "client_secret" not in body