Armis
Use the Armis integration to search alerts and devices, tag and untag devices, and set alert statuses.
Utilities · Armis
Details
| ID | Armis |
|---|---|
| Provider | Insight Partners |
| Category | Utilities |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Use the Armis integration to search alerts and devices, tag and untag devices, and set alert statuses.
This integration was integrated and tested with the latest version of Armis.
Configure Armis in Cortex
| Parameter | Description | Required |
|---|---|---|
| Server URL | True | |
| Fetch incidents | False | |
| Incident type | False | |
| Maximum number of incidents per fetch | False | |
| Fetch alerts with status (UNHANDLED, SUPPRESSED, RESOLVED) | False | |
| Fetch alerts with type | The type of alerts are Policy Violation, System Policy Violation, Anomaly Detection If no type is chosen, all types will be fetched. | False |
| Minimum severity of alerts to fetch | True | |
| First fetch time | The date or relative timestamp from which to begin fetching alerts. Supported formats: 2 minutes, 2 hours, 2 days, 2 weeks, 2 months, 2 years, yyyy-mm-dd, yyyy-mm-ddTHH:MM:SSZ. For example: 01 April 2025, 01 March 2025 04:45:33, 2025-02-17T14:05:44Z. |
False |
| Trust any certificate (not secure) | False | |
| Secret API Key | True | |
| Fetch Alerts AQL | Use this parameter to fetch incidents using a free AQL string rather than the simpler alert type, severity, etc. | False |
| Proxy | Whether to use the System proxy | False |
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
armis-search-alerts
Search Armis Alerts.
Base Command
armis-search-alerts
Input
| Argument Name | Description | Required |
|---|---|---|
| max_results | The maximum number of results to get. Default is 50. | Optional |
| time_frame | Filter by start time. Examples: “3 days ago” “1 month” “2019-10-10T12:22:00” “2019-10-10”. Default is 3 days. |
Optional |
| alert_id | The ID of the alert. | Optional |
| severity | A comma-separated list of alert severity levels by which to filter the search results. Possible values: “Low”, “Medium”, and “High”. | Optional |
| status | A comma-separated list of alert statuses by which to filter the search results. Possible values: “UNHANDLED”, “SUPPRESSED”, and “RESOLVED”. | Optional |
| alert_type | A comma-separated list of alert types by which to filter the search results. Possible values: “Policy Violation”, “System Policy Violation”, and “Anomaly Detection” | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Armis.Alert.activityIds | Number | The activity IDs of the alert. |
| Armis.Alert.activityUUIDs | String | The activity UUIDs of the alert. |
| Armis.Alert.alertId | Number | The ID of the alert. |
| Armis.Alert.connectionIds | Number | The connection IDs of the alert. |
| Armis.Alert.description | String | A text description of the alert. |
| Armis.Alert.deviceIds | Number | The device IDs of the alert |
| Armis.Alert.severity | String | The severity of the alert. |
| Armis.Alert.status | String | The status of the alert. |
| Armis.Alert.time | Date | The date and time the alert occurred. |
| Armis.Alert.title | String | The title of the alert. |
| Armis.Alert.type | String | The type of the alert. |
Command Example
!armis-search-alerts status=RESOLVED max_results=10
Context Example
{
"Armis": {
"Alert": {
"activityIds": [
23314066,
23316462,
23317202,
23326470,
23341779,
23342441
],
"activityUUIDs": [
"enyZFHgBAAAC-vCT9nJG",
"0Hy2FHgBAAAC-vCTGnJB",
"3Hy_FHgBAAAC-vCTp3Kz",
"v3wSFXgBAAAC-vCTFnNL",
"_nxOGHgBAAAC-vCTUnc2",
"2HxpGHgBAAAC-vCT03jo"
],
"alertId": 3984,
"connectionIds": [
923419,
923501,
924451
],
"description": "Smart TV started connection to Corporate Network",
"deviceIds": [
165722,
532
],
"severity": "Medium",
"status": "Resolved",
"time": "2021-03-09T01:28:44.032944+00:00",
"title": "Smart TV connected to Corporate network",
"type": "System Policy Violation"
}
}
}
Human Readable Output
Alerts
Severity Type Time Status Title Description Activity Ids Activity UUI Ds Alert Id Connection Ids Device Ids Medium System Policy Violation 2021-03-09T01:28:44.032944+00:00 Resolved Smart TV connected to Corporate network Smart TV started connection to Corporate Network 23314066,
23316462,
23317202,
23326470,
23341779,
23342441enyZFHgBAAAC-vCT9nJG,
0Hy2FHgBAAAC-vCTGnJB,
3Hy_FHgBAAAC-vCTp3Kz,
v3wSFXgBAAAC-vCTFnNL,
_nxOGHgBAAAC-vCTUnc2,
2HxpGHgBAAAC-vCT03jo3984 923419,
923501,
924451165722,
532
armis-update-alert-status
Updates the status for an alert.
Base Command
armis-update-alert-status
Input
| Argument Name | Description | Required |
|---|---|---|
| alert_id | The ID of the alert to update. | Required |
| status | New status of the alert. Possible values are: UNHANDLED, RESOLVED, SUPPRESSED. | Required |
Context Output
There is no context output for this command.
Command Example
#### Human Readable Output
### armis-search-alerts-by-aql-string
***
Searches the alerts with a raw AQL string.
#### Base Command
`armis-search-alerts-by-aql-string`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| aql_string | The AQL string to by which to search. | Required |
| max_results | The maximum number of results to get. Default is 50. | Optional |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Armis.Alert.activityIds | Number | The activity IDs of the alert. |
| Armis.Alert.activityUUIDs | String | The activity UUIDs of the alert. |
| Armis.Alert.alertId | Number | The ID of the alert. |
| Armis.Alert.connectionIds | Number | The connection IDs of the alert. |
| Armis.Alert.description | String | The description of the alert. |
| Armis.Alert.deviceIds | Number | The device IDs of the alert. |
| Armis.Alert.severity | String | The severity of the alert. |
| Armis.Alert.status | String | The status of the alert. |
| Armis.Alert.time | Date | The date and time the alert occurred. |
| Armis.Alert.title | String | The title of the alert. |
| Armis.Alert.type | String | The type of the alert. |
#### Command Example
```!armis-search-alerts-by-aql-string aql_string="alertId:(3821)"```
#### Context Example
```json
{
"Armis": {
"Alert": {
"activityIds": [
22060159
],
"activityUUIDs": [
"nTiGqXcBAAAC-vCTfzPN"
],
"alertId": 3821,
"connectionIds": [],
"description": "The Armis security platform has detected a violation of a policy and generated an alert.",
"deviceIds": [
199808
],
"severity": "Medium",
"status": "Resolved",
"time": "2021-02-16T06:23:02.101479+00:00",
"title": "Unencrypted Traffic: SMB",
"type": "System Policy Violation"
}
}
}
Human Readable Output
Alerts
Alert Id Description Type Title Severity Status Time Activity Ids Activity UUI Ds Device Ids 3821 The Armis security platform has detected a violation of a policy and generated an alert. System Policy Violation Unencrypted Traffic: SMB Medium Resolved 2021-02-16T06:23:02.101479+00:00 22060159 nTiGqXcBAAAC-vCTfzPN 199808
armis-tag-device
Adds a tag to a device.
Base Command
armis-tag-device
Input
| Argument Name | Description | Required |
|---|---|---|
| device_id | The ID of the device to add a tag to. | Required |
| tags | The tags to add to the device. | Required |
Context Output
There is no context output for this command.
Command Example
!armis-tag-device device_id=165722 tags=test
Human Readable Output
Successfully Tagged device: 165722 with tags: [‘test’]
armis-untag-device
Removes a tag from a device.
Base Command
armis-untag-device
Input
| Argument Name | Description | Required |
|---|---|---|
| device_id | The ID of the device to remove a tag from. | Required |
| tags | The tags to remove from the device. | Required |
Context Output
There is no context output for this command.
Command Example
!armis-untag-device device_id=165722 tags=test
Human Readable Output
Successfully Untagged device: 165722 with tags: [‘test’]
armis-search-devices
Search devices by identifiers.
Base Command
armis-search-devices
Input
| Argument Name | Description | Required |
|---|---|---|
| name | The name of the device to search for. | Optional |
| device_id | The ID of the device to search for. | Optional |
| mac_address | The MAC address of the device to search for. | Optional |
| ip_address | The IP address of the device to search for. | Optional |
| device_type | A comma-separated list of device types by which to filter the results. for example “Routers”, “Laptops”, “IP Cameras” (there are many device types. for a full list access your Armis instance). | Optional |
| time_frame | The time frame of the device to search for. | Optional |
| max_results | The maximum number of results to get. Default is 50. | Optional |
| risk_level | A comma-separated list of device risk levels by which to filter the results. Possible values: “Low”, “Medium”, and “High”. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Armis.Device.accessSwitch | String | The access switch of the device. |
| Armis.Device.category | String | The category of the device. |
| Armis.Device.firstSeen | Date | The first time the device was seen. |
| Armis.Device.id | Number | The ID of the device. |
| Armis.Device.ipaddress | String | The IP address of the device. |
| Armis.Device.ipv6 | String | The IPv6 address of the device. |
| Armis.Device.lastSeen | Date | The last time the device was seen. |
| Armis.Device.macAddress | String | The MAC address of the device. |
| Armis.Device.manufacturer | String | The manufacturer of the device. |
| Armis.Device.model | String | The model of the device. |
| Armis.Device.name | String | The name of the device. |
| Armis.Device.operatingSystem | String | The operating system of the device. |
| Armis.Device.operatingSystemVersion | String | The operating system version of the device. |
| Armis.Device.purdueLevel | String | The purdue level of the device. |
| Armis.Device.riskLevel | String | The risk level of the device. |
| Armis.Device.sensor | String | The sensor of the device. |
| Armis.Device.site | String | The site of the device. |
| Armis.Device.tags | String | The tags of the device. |
| Armis.Device.type | String | The type of the device. |
| Armis.Device.user | String | The user of the device. |
| Armis.Device.visibility | String | The visibility of the device. |
Command example
!armis-search-devices device_id=2172
Context Example
{
"Armis": {
"Device": {
"accessSwitch": null,
"boundaries": "Corporate",
"category": "Computers",
"customProperties": {},
"dataSources": [
{
"firstSeen": "2022-08-11T07:54:32.167939+00:00",
"lastSeen": "2022-11-28T14:56:36.198248+00:00",
"name": "Active Directory",
"types": [
"Asset & System Management",
"Identity Provider"
]
},
{
"firstSeen": "2021-08-15T07:37:58.891683+00:00",
"lastSeen": "2022-11-28T21:02:20.208248+00:00",
"name": "CrowdStrike",
"types": [
"Agent Based",
"Endpoint Protection"
]
},
{
"firstSeen": "2022-07-09T07:50:51.190248+00:00",
"lastSeen": "2022-11-28T20:02:52.190248+00:00",
"name": "MBAM (BitLocker)",
"types": [
"Asset & System Management"
]
},
{
"firstSeen": "2022-11-16T13:55:23.190248+00:00",
"lastSeen": "2022-11-28T21:30:45.190248+00:00",
"name": "Palo Alto Networks GlobalProtect",
"types": [
"Firewall"
]
},
{
"firstSeen": "2022-07-12T12:55:47.190248+00:00",
"lastSeen": "2022-11-28T23:42:41.190248+00:00",
"name": "Qualys",
"types": [
"Vulnerability Management"
]
},
{
"firstSeen": "2022-07-09T07:50:51.190248+00:00",
"lastSeen": "2022-11-28T20:02:52.190248+00:00",
"name": "SCCM",
"types": [
"Asset & System Management",
"Patch Management"
]
},
{
"firstSeen": "2022-07-22T06:36:52.190248+00:00",
"lastSeen": "2022-07-22T06:40:36.190248+00:00",
"name": "ServiceNow",
"types": [
"Asset & System Management"
]
},
{
"firstSeen": "2022-11-21T17:00:00.360310+00:00",
"lastSeen": "2022-11-28T22:23:35.190248+00:00",
"name": "Traffic Inspection",
"types": [
"Traffic Inspection",
"Data Analysis"
]
},
{
"firstSeen": "2022-06-13T07:10:57.686241+00:00",
"lastSeen": "2022-11-22T00:59:54.686241+00:00",
"name": "User",
"types": [
"Data Upload"
]
},
{
"firstSeen": "2022-11-15T10:05:08.190248+00:00",
"lastSeen": "2022-11-28T21:30:45.190248+00:00",
"name": "Aruba WLC",
"types": [
"WLC"
]
}
],
"firstSeen": "2022-11-21T16:59:58.360310+00:00",
"id": 2172,
"ipAddress": "10.77.27.183",
"ipv6": "fe80::647b:ba0f:9628:6014",
"lastSeen": "2022-11-29T18:42:50.190248+00:00",
"macAddress": "50:76:AF:D3:3F:AB",
"manufacturer": "Lenovo",
"model": "ThinkPad X1 Yoga 3rd Gen",
"name": "000000731194pc.corporate.acme.com",
"operatingSystem": "Windows",
"operatingSystemVersion": "10",
"purdueLevel": 4,
"riskLevel": 5,
"sensor": {
"name": "PALO_ALTO-IDF04-SW01:Gig1/0/44 Enterprise",
"type": "Access Switch"
},
"site": {
"location": "Palo Alto",
"name": "Palo Alto Enterprise"
},
"tags": [
"Corporate",
"ServiceNow",
"SCCM"
],
"type": "Laptops",
"userIds": [
12
],
"visibility": "Full"
}
}
}
Human Readable Output
Devices
Risk Level Id Name Type Ip Address Ipv 6 Mac Address Operating System Operating System Version Manufacturer Model Tags 5 2172 000000731194pc.corporate.acme.com Laptops 10.77.27.183 fe80::647b:ba0f:9628:6014 50:76:AF:D3:3F:AB Windows 10 Lenovo ThinkPad X1 Yoga 3rd Gen Corporate,
ServiceNow,
SCCM
armis-search-devices-by-aql
Searches devices with a custom AQL search string.
Base Command
armis-search-devices-by-aql
Input
| Argument Name | Description | Required |
|---|---|---|
| aql_string | The AQL string. | Required |
| max_results | The maximum number of results to get. Default is 50. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| Armis.Device.accessSwitch | String | The access switch of the device. |
| Armis.Device.category | String | The category of the device. |
| Armis.Device.firstSeen | Date | The first time the device was seen. |
| Armis.Device.id | Number | The ID of the device. |
| Armis.Device.ipaddress | String | The IP address of the device. |
| Armis.Device.ipv6 | String | The IPv6 address of the device. |
| Armis.Device.lastSeen | Date | The last time the device was seen. |
| Armis.Device.macAddress | String | The MAC address of the device. |
| Armis.Device.manufacturer | String | The manufacturer of the device. |
| Armis.Device.model | String | The model of the device. |
| Armis.Device.name | String | The name of the device. |
| Armis.Device.operatingSystem | String | The operating system of the device. |
| Armis.Device.operatingSystemVersion | String | The operating system version of the device. |
| Armis.Device.purdueLevel | String | The purdue level of the device. |
| Armis.Device.riskLevel | String | The risk level of the device. |
| Armis.Device.sensor | String | The sensor of the device. |
| Armis.Device.site | String | The site of the device. |
| Armis.Device.tags | String | The tags of the device. |
| Armis.Device.type | String | The type of the device. |
| Armis.Device.user | String | The user of the device. |
| Armis.Device.visibility | String | The visibility of the device. |
Command Example
!armis-search-devices-by-aql aql_string="macAddress:(a4:5d:36:c5:32:69)"
Context Example
{
"Armis": {
"Device": {
"accessSwitch": "win-sw-hoc-01:po9",
"category": "Computers",
"dataSources": [
{
"firstSeen": "2020-10-01T11:56:48+00:00",
"lastSeen": "2021-03-11T20:26:40+00:00",
"name": "Meraki",
"types": [
"WLC"
]
},
{
"firstSeen": "2021-02-02T08:34:10.536715+00:00",
"lastSeen": "2021-03-11T20:21:22.374047+00:00",
"name": "Network Mapper",
"types": [
"Network Monitoring"
]
},
{
"firstSeen": "2020-07-05T11:25:24.128383+00:00",
"lastSeen": "2021-03-11T20:32:33.494314+00:00",
"name": "Traffic Inspection",
"types": [
"Traffic Inspection",
"Data Analysis"
]
}
],
"firstSeen": "2020-06-01T00:30:32.318087+00:00",
"id": 74745,
"ipAddress": "10.0.100.10",
"ipv6": null,
"lastSeen": "2021-03-11T20:32:33.494314+00:00",
"macAddress": "a4:5d:36:c5:32:69",
"manufacturer": "Hewlett Packard",
"model": "Hewlett device",
"name": "wc-shoretel.winslow.local",
"operatingSystem": "Windows",
"operatingSystemVersion": "Server 2008 R2",
"riskLevel": 10,
"sensor": {
"name": "win-wap-tfm-01",
"type": "Access Point"
},
"site": {
"location": "28 Merri Concourse, Campbellfield VIc 3061 Australia",
"name": "Winslow Small Plant"
},
"tags": [
"MERAKI_NETWORK=Winslow Campbellfield"
],
"type": "Servers",
"user": "",
"visibility": "Full"
}
}
}
Human Readable Output
Devices
Risk Level Name Type Ip Address Tags Id 10 wc-shoretel.winslow.local Servers 10.0.100.10 MERAKI_NETWORK=Winslow Campbellfield 74745
Configuration parameters
url— Server URL (required)isFetch— Fetch incidentsincidentType— Incident typeincidentFetchInterval— Incidents Fetch Intervalmax_fetch— Maximum number of incidents per fetchalert_status— Fetch alerts with status (UNHANDLED,SUPPRESSED,RESOLVED)alert_type— Fetch alerts with typemin_severity— Minimum severity of alerts to fetch (required)first_fetch— First fetch timeinsecure— Trust any certificate (not secure)secret— Secret API Key (required)free_fetch_string— Fetch Alerts AQLproxy— Use system proxy settings
Commands (7)
-
armis-search-alertsSearch Armis alerts.
-
armis-search-alerts-by-aql-stringSearch the alerts with a raw AQL string.
-
armis-search-devicesSearch devices by identifiers.
-
armis-search-devices-by-aqlSearch devices with a custom AQL search string.
-
armis-tag-deviceAdd a tag to a device.
-
armis-untag-deviceRemove a tag from a device.
-
armis-update-alert-statusUpdate the status for an alert.
"""IMPORTS""" import pytz import urllib3 from CommonServerPython import * # Disable insecure warnings urllib3.disable_warnings() """ CONSTANTS """ DEFAULT_FIRST_FETCH = "3 days" DEFAULT_MAX_FETCH = "10" class Client(BaseClient): def __init__(self, secret: str, base_url: str, verify: bool, proxy): super().__init__(base_url, verify=verify, proxy=proxy) self._secret = secret def http_request( self, method="GET", url_suffix=None, resp_type="json", headers=None, json_data=None, params=None, data=None ) -> Any: """ Function to make http requests using inbuilt _http_request() method. Handles token expiration case and makes request using secret key. Args: method (str): HTTP method to use. Defaults to "GET". url_suffix (str): URL suffix to append to base_url. Defaults to None. resp_type (str): Response type. Defaults to "json". headers (dict): Headers to include in the request. Defaults to None. json_data (dict): JSON data to include in the request body. Defaults to None. params (dict): Parameters to include in the request. Defaults to None. data (dict): Data to include in the request body. Defaults to None. Returns: Any: Response from the request. """ headers = headers or {} try: token = self._get_token() headers["Authorization"] = str(token) response = self._http_request( method=method, url_suffix=url_suffix, params=params, json_data=json_data, headers=headers, resp_type=resp_type, data=data, ) except DemistoException as e: if "Error in API call [401]" in str(e): demisto.debug(f"One retry for 401 error. Error: {str(e)}") # Token has expired, refresh token and retry request token = self._get_token(force_new=True) headers["Authorization"] = str(token) response = self._http_request( method=method, url_suffix=url_suffix, params=params, json_data=json_data, headers=headers, resp_type=resp_type, data=data, ) else: raise e return response def is_token_expired(self) -> bool: demisto.debug("Checking if token is expired") token_expiration = get_integration_context().get("token_expiration", None) if token_expiration is not None: expire_time = dateparser.parse(token_expiration).replace(tzinfo=datetime.now().tzinfo) # type: ignore current_time = datetime.now() - timedelta(seconds=30) demisto.debug(f"Comparing current time: {current_time} with expire time: {expire_time}") return expire_time < current_time else: return True def _get_token(self, force_new: bool = False): """ Returns an existing access token if a valid one is available and creates one if not Args: force_new (bool): create a new access token even if an existing one is available Returns: str: A valid Access Token to authorize requests """ token = get_integration_context().get("token", None) if token is None or force_new or self.is_token_expired(): demisto.debug("Creating a new access token") response = self._http_request("POST", "/access_token/", data={"secret_key": self._secret}) token = response.get("data", {}).get("access_token") expiration = response.get("data", {}).get("expiration_utc") expiration_date = dateparser.parse(expiration) assert expiration_date is not None, f"failed parsing {expiration}" set_integration_context({"token": token, "token_expiration": str(expiration_date)}) demisto.debug(f"setting new token to integration context with expiration time: {expiration_date}.") return token return token def search_by_aql_string(self, aql_string: str, order_by: str = None, max_results: int = None, page_from: int = None): """ Search with an AQL string and return the results. This function exists to allow a more advanced search than is provided for by the basic search alerts and search devices functions Args: aql_string (str): The AQL String by which to search order_by (str): what attribute to order the results by (time, etc') max_results (int): The maximum number of results to return page_from (int): Start from this number result - skip this many results Returns: dict: A JSON containing a list of results represented by JSON objects """ params = {"aql": aql_string} if order_by is not None: params["orderBy"] = order_by if max_results is not None: params["length"] = str(max_results) if page_from is not None: params["from"] = str(page_from) response = self.http_request("GET", "/search/", params=params, headers={"accept": "application/json"}) if max_results is None: # if max results was not specified get all results. results: list = response.get("data", {}).get("results") while response.get("data", {}).get("next") is not None: # while the response says there are more results use the 'page from' parameter to get the next results params["from"] = str(len(results)) response = self.http_request("GET", "/search/", params=params, headers={"accept": "application/json"}) results.extend(response.get("data", {}).get("results", [])) response["data"]["results"] = results return response["data"] def search_alerts( self, severity: list[str] = None, status: list[str] = None, alert_type: list[str] = None, alert_id: str = None, time_frame: str = None, order_by: str = None, max_results: int = None, page_from: int = None, ): """ Search Alerts based on commonly used parameters Args: severity (List[str]): The severities of Alerts to include status (List[str]): The statuses of Alerts to include alert_type (List[str]): The types of Alerts to include alert_id (str): The Id of a specific Alert to filter by time_frame (str): A time from for the creation of the Alerts order_by (str): Order results by this attribute max_results (int): The maximum number of results to return page_from (int): Start from this number result - skip this many results Returns: dict: A JSON containing a list of matching Alerts represented by JSON objects """ time_frame = "3 Days" if time_frame is None else time_frame aql_string = ["in:alerts", f'timeFrame:"{time_frame}"'] if severity: severity_string = ",".join(list(severity)) aql_string.append(f"riskLevel:{severity_string}") # noqa: E231 if status: status_string = ",".join(list(status)) aql_string.append(f"status:{status_string}") # noqa: E231 if alert_type: alert_string = ",".join([f'"{alert_option}"' for alert_option in alert_type]) aql_string.append(f"type:{alert_string}") # noqa: E231 if alert_id: aql_string.append(f"alertId:({alert_id})") # noqa: E231 aql_string = " ".join(aql_string) # type: ignore # type: ignore return self.search_by_aql_string(aql_string, order_by=order_by, max_results=max_results, page_from=page_from) def free_string_search_alerts(self, aql_string: str, order_by: str = None, max_results: int = None, page_from: int = None): """ Search Alerts using a custom AQL String Args: aql_string (str): The AQL String by which to search order_by (str): Order results by this attribute max_results (int): The maximum number of results to return page_from (int): Start from this number result - skip this many results Returns: dict: A JSON containing a list of matching Alerts represented by JSON objects """ return self.search_by_aql_string( f"in:alerts {aql_string}", # noqa: E231 order_by=order_by, max_results=max_results, page_from=page_from, ) def update_alert_status(self, alert_id: str, status: str): """ Update the status of an Alert Args: status (str): The new status of the Alert to set alert_id (str): The Id of the Alert """ return self.http_request( "PATCH", f"/alerts/{alert_id}/", headers={ "accept": "application/json", "content-type": "application/x-www-form-urlencoded", }, data={"status": status}, ) def tag_device(self, device_id: str, tags: list[str]): """ Add tags to a Device Args: tags (str): The tags to add to the Device device_id (str): The Id of the Device """ return self.http_request( "POST", f"/devices/{device_id}/tags/", json_data={"tags": tags}, headers={"accept": "application/json"}, ) def untag_device(self, device_id: str, tags: list[str]): """ Remove tags from a Device Args: tags (List[str]): The tags to remove from the Device device_id (str): The Id of the Device """ return self.http_request( "DELETE", f"/devices/{device_id}/tags/", json_data={"tags": tags}, headers={"accept": "application/json"}, ) def search_devices( self, name: str = None, device_id: str = None, mac_address: str = None, risk_level: list[str] = None, ip_address: str = None, device_type: list[str] = None, time_frame: str = None, order_by: str = None, max_results: int = None, ): """ Search Devices using commonly used search parameters Args: name (str): The name of the Device device_id (str): The Id of a Device mac_address (str): The MAC Address of the Device risk_level (List[str]): The risk level to filter by ip_address (str): the IP Address of the Device device_type (List[str]): The type of Device to filter by time_frame (str): A time frame to filter by order_by (str): Order results by this attribute max_results (int): The maximum number of results to return Returns: dict: A JSON containing a list of matching Devices represented by JSON objects """ time_frame = "3 Days" if time_frame is None else time_frame aql_string = ["in:devices", f'timeFrame:"{time_frame}"'] if name is not None: aql_string.append(f"name:({name})") # noqa: E231 if device_type is not None: type_string = ",".join([f'"{type_option}"' for type_option in device_type]) aql_string.append(f"type:{type_string}") # noqa: E231 if mac_address is not None: aql_string.append(f"macAddress:({mac_address})") # noqa: E231 if ip_address is not None: aql_string.append(f"ipAddress:({ip_address})") # noqa: E231 if device_id is not None: aql_string.append(f"deviceId:({device_id})") # noqa: E231 if risk_level is not None: risk_level_string = ",".join(list(risk_level)) aql_string.append(f"riskLevel:{risk_level_string}") # noqa: E231 aql_string = " ".join(aql_string) # type: ignore return self.search_by_aql_string(aql_string, order_by=order_by, max_results=max_results) # type: ignore def free_string_search_devices(self, aql_string: str, order_by: str = None, max_results: int = None): """ Search Devices using commonly used search parameters Args: aql_string (str): The AQL Sgtring by which to search order_by (str): Order results by this attribute max_results (int): The maximum number of results to return Returns: dict: A JSON containing a list of matching Devices represented by JSON objects """ return self.search_by_aql_string(f"in:devices {aql_string}", order_by=order_by, max_results=max_results) # noqa: E231 def test_module(client: Client, params: dict): """ Returning 'ok' indicates that the integration works like it is supposed to. Connection to the service is successful. This test works by using a Client instance to create a temporary access token using the provided secret key, thereby testing both the connection to the server and the validity of the secret key Args: client: Armis client params: A dictionary containing the parameters provided by the user. Returns: 'ok' if test passed, anything else will fail the test. """ try: if argToBoolean(params.get("isFetch", False)): demisto.debug("Calling fetch incidents") first_fetch_time, minimum_severity, alert_type, alert_status, free_search_string, max_fetch = get_fetch_params(params) fetch_incidents( client, {}, first_fetch_time, minimum_severity, alert_type, alert_status, # type: ignore free_search_string, max_fetch, is_test=True, ) # type: ignore else: client._get_token(force_new=True) return "ok" except Exception as e: return f"Test failed with the following error: {repr(e)}" def get_fetch_params(params: dict) -> tuple: """ Get the tuple of parameters required for calling fetch incidents Args: params: A dictionary containing the parameters provided by the user Returns: tuple: A tuple containing the first fetch time, minimum severity, alert type, alert status, free search string and max fetch """ first_fetch_time = arg_to_datetime(params.get("first_fetch") or DEFAULT_FIRST_FETCH) minimum_severity = params.get("min_severity") alert_type = params.get("alert_type") alert_status = params.get("alert_status") free_search_string = params.get("free_fetch_string") max_fetch = arg_to_number(params.get("max_fetch") or DEFAULT_MAX_FETCH) return first_fetch_time, minimum_severity, alert_type, alert_status, free_search_string, max_fetch def _ensure_timezone(date: datetime): """ Some datetime objects are timezone naive and these cannot be compared to timezone aware datetime objects. This function sets a default timezone of UTC for any object without a timezone Args: date (datetime): The date object to add a timezone to Returns: datetime: A timezone aware datetime object """ if date.tzinfo is None: return date.replace(tzinfo=pytz.UTC) return date def _create_time_frame_string(last_fetch: datetime): """ The function receives the last_fetch time and returns a string formatted to Armis' requirements Armis' smallest unit is seconds so the function rounds the result to seconds Args: last_fetch (datetime): The date object of the last fetch Returns: time_frame_string: An Armis' compatible time frame string based on the last_fetch time """ current_time = _ensure_timezone(datetime.now()) time_frame_seconds = round((current_time - last_fetch).total_seconds()) time_frame_string = f"{time_frame_seconds} seconds" return time_frame_string def fetch_incidents( client: Client, last_run: dict, first_fetch_time: Optional[datetime], minimum_severity: str, alert_type: list[str], alert_status: list[str], free_search_string: str, max_results: Optional[int], is_test: bool = False, ): """ This function will execute each interval (default is 1 minute). Args: client (Client): Armis client last_run (dict): The greatest incident created_time we fetched from last fetch first_fetch_time (Optional[datetime]): If last_run is None then fetch all incidents since first_fetch_time minimum_severity (str): the minimum severity of alerts to fetch alert_type (List[str]): the type of alerts to fetch alert_status (List[str]): the status of alerts to fetch free_search_string (str): A custom search string for fetching alerts max_results: (Optional[int]): The maximum number of alerts to fetch at once is_test (bool): A boolean indicating whether the command is being run in test mode. Returns: next_run: This will be last_run in the next fetch-incidents incidents: Incidents that will be created in Demisto """ # Get the last fetch time, if exists last_fetch = last_run.get("last_fetch") latest_alert_fetch = last_run.get("latest_alert_fetch") if latest_alert_fetch: latest_alert_fetch_date = dateparser.parse(latest_alert_fetch) assert latest_alert_fetch_date is not None latest_alert_fetch = _ensure_timezone(latest_alert_fetch_date) incomplete_fetches = last_run.get("incomplete_fetches", 0) # Handle first time fetch if last_fetch: last_fetch_date = dateparser.parse(last_fetch) assert last_fetch_date is not None, f"failed parsing {last_fetch}" last_fetch = _ensure_timezone(last_fetch_date) else: last_fetch_time_date = first_fetch_time assert last_fetch_time_date is not None last_fetch = _ensure_timezone(last_fetch_time_date) # use the last fetch time to build a time frame in which to search for alerts. time_frame = _create_time_frame_string(last_fetch) latest_created_time = _ensure_timezone(last_fetch) # get a list of severities from the minimum specified and upward # for example if min_severity is Medium requested_severities will be ['Medium', 'High'] severities_in_order = ["Low", "Medium", "High"] requested_severities = severities_in_order[severities_in_order.index(minimum_severity) :] # noqa: E203 incidents = [] # when the previous fetch returned more than max_results alerts, the same query is made again and max_results alerts # are skipped using the page_from parameter. in a case where multiple fetches were incomplete max_results times the # number of incomplete fetches must be skipped in order to prevent duplicating incidents page_from = max_results * incomplete_fetches or None if free_search_string: data = client.free_string_search_alerts( f"{free_search_string} timeFrame:{time_frame}", # noqa: E231 order_by="time", max_results=max_results, page_from=page_from, ) else: data = client.search_alerts( status=alert_status, severity=requested_severities, alert_type=alert_type, time_frame=time_frame, order_by="time", max_results=max_results, page_from=page_from, ) if is_test: return last_run, [] for alert in data.get("results", []): time_date = dateparser.parse(alert.get("time")) assert time_date is not None incident_created_time = _ensure_timezone(time_date) # Alert was already fetched. Skipping if latest_alert_fetch and latest_alert_fetch >= incident_created_time: continue incident = { "name": alert.get("description"), "occurred": incident_created_time.strftime("%Y-%m-%dT%H:%M:%SZ"), "rawJSON": json.dumps(alert), } incidents.append(incident) # Update last run and add incident if the incident is newer than last fetch if incident_created_time > latest_created_time: latest_created_time = incident_created_time latest_alert_fetch_iso_format = latest_created_time.isoformat() if data.get("next"): # if more than max_results alerts were returned, this fetch is incomplete and the extra results must be fetched # next time next_run = { "last_fetch": last_fetch.isoformat(), "incomplete_fetches": incomplete_fetches + 1, "latest_alert_fetch": latest_alert_fetch_iso_format, } else: next_run = { "last_fetch": latest_alert_fetch_iso_format, "incomplete_fetches": 0, "latest_alert_fetch": latest_alert_fetch_iso_format, } return next_run, incidents def untag_device_command(client: Client, args: dict): """ armis-untag-device command: Remove tags from a Device Args: client (Client): An Armis client object args (dict): A dict object containing the arguments for this command """ device_id = str(args.get("device_id")) tags = argToList(args.get("tags")) client.untag_device(device_id, tags) return f"Successfully Untagged device: {device_id} with tags: {tags}" def tag_device_command(client: Client, args: dict): """ armis-tag-device command: Add the given tags to a device Args: client (Client): An Armis client object args (dict): A dict object containing the arguments for this command """ device_id = str(args.get("device_id")) tags = argToList(args.get("tags")) client.tag_device(device_id, tags) return f"Successfully Tagged device: {device_id} with tags: {tags}" def update_alert_status_command(client: Client, args: dict): """ armis-update-alert-status command: Update the status of an Alert to the given status Args: client (Client): An Armis client object args (dict): A dict object containing the arguments for this command """ alert_id = str(args.get("alert_id")) status = str(args.get("status")) client.update_alert_status(alert_id, status) return f"Successfully Updated Alert: {alert_id} to status: {status}" def search_alerts_command(client: Client, args: dict): """ armis-search-alerts command: Returns results for searching Alerts by common parameters Args: client (Client): An Armis client object args (dict): A dict object containing the arguments for this command Returns: CommandResults: A CommandResults object containing the matching alerts """ severity = args.get("severity") if severity is not None: severity = argToList(severity) status = args.get("status") if status is not None: status = argToList(status) alert_type = args.get("alert_type") if alert_type is not None: alert_type = argToList(alert_type) alert_id = args.get("alert_id") max_results = int(args.get("max_results", 50)) time_frame = args.get("time_frame") response = client.search_alerts(severity, status, alert_type, alert_id, time_frame, max_results=max_results) results = response.get("results") if results: return CommandResults( outputs_prefix="Armis.Alert", outputs_key_field="alertId", outputs=results, raw_response=response, readable_output=tableToMarkdown( "Alerts", results, headers=[ "severity", "type", "time", "status", "title", "description", "activityIds", "activityUUIDs", "alertId", "connectionIds", "deviceIds", ], removeNull=True, headerTransform=pascalToSpace, ), ) return "No results found" def search_devices_command(client: Client, args: dict): """ armis-search-devices command: Returns results for searching Devices by common parameters Args: client (Client): An Armis client object args (dict): A dict object containing the arguments for this command """ risk_level = args.get("risk_level") if risk_level is not None: risk_level = argToList(risk_level) device_type = args.get("device_type") if device_type is not None: device_type = argToList(device_type) name = args.get("name") device_id = args.get("device_id") mac_address = args.get("mac_address") ip_address = args.get("ip_address") time_frame = args.get("time_frame") max_results = int(args.get("max_results", 50)) response = client.search_devices( name, device_id, mac_address, risk_level, ip_address, device_type, time_frame, max_results=max_results ) results = response.get("results") if results: headers = [ "riskLevel", "id", "name", "type", "ipAddress", "ipv6", "macAddress", "operatingSystem", "operatingSystemVersion", "manufacturer", "model", "tags", "user", ] return CommandResults( outputs_prefix="Armis.Device", outputs_key_field="deviceId", outputs=results, raw_response=response, readable_output=tableToMarkdown("Devices", results, headers=headers, removeNull=True, headerTransform=pascalToSpace), ) return "No devices found" def search_devices_by_aql_command(client: Client, args: dict): """ armis-search-devices-by-aql command: Returns results for searching Devices using a free AQL string Args: client (Client): An Armis client object args (dict): A dict object containing the arguments for this command """ aql_string = str(args.get("aql_string")) max_results = int(args.get("max_results", 50)) response = client.free_string_search_devices(aql_string, max_results=max_results) results = response.get("results") if results: headers = [ "riskLevel", "name", "type", "ipAddress", "tags", "user", "id", ] return CommandResults( outputs_prefix="Armis.Device", outputs_key_field="deviceId", outputs=results, raw_response=response, readable_output=tableToMarkdown("Devices", results, headers=headers, removeNull=True, headerTransform=pascalToSpace), ) return "No devices found" def search_alerts_by_aql_command(client: Client, args: dict): """ armis-search-alerts-by-aql command: Returns results for searching Alerts using a free AQL string Args: client (Client): An Armis client object args (dict): A dict object containing the arguments for this command """ aql_string = str(args.get("aql_string")) max_results = int(args.get("max_results", 50)) response = client.free_string_search_alerts(aql_string, max_results=max_results) results = response.get("results") if results: return CommandResults( outputs_prefix="Armis.Alert", outputs_key_field="alertId", outputs=results, raw_response=response, readable_output=tableToMarkdown( "Alerts", results, headers=[ "alertId", "description", "type", "title", "severity", "status", "time", "activityIds", "activityUUIDs", "connectionIds", "deviceIds", ], removeNull=True, headerTransform=pascalToSpace, ), ) return "No alerts found" def main(): """ PARSE AND VALIDATE INTEGRATION PARAMS """ params = demisto.params() command = demisto.command() args = demisto.args() secret = params.get("secret") # get the service API url base_url = params.get("url") if "api/v1" not in base_url: base_url = urljoin(base_url, "/api/v1/") verify = not params.get("insecure", False) proxy = params.get("proxy", False) demisto.info(f"Command being called is {command}") try: client = Client(secret, base_url=base_url, verify=verify, proxy=proxy) if command == "test-module": # This is the call made when pressing the integration Test button. result = test_module(client, params) return_results(result) elif command == "fetch-incidents": first_fetch_time, minimum_severity, alert_type, alert_status, free_search_string, max_fetch = get_fetch_params(params) # Set and define the fetch incidents command to run after activated via integration settings. next_run, incidents = fetch_incidents( client=client, last_run=demisto.getLastRun(), first_fetch_time=first_fetch_time, alert_type=alert_type, alert_status=alert_status, minimum_severity=minimum_severity, free_search_string=free_search_string, max_results=max_fetch, ) demisto.setLastRun(next_run) demisto.incidents(incidents) elif command == "armis-search-alerts": return_results(search_alerts_command(client, args)) elif command == "armis-update-alert-status": return_results(update_alert_status_command(client, args)) elif command == "armis-tag-device": return_results(tag_device_command(client, args)) elif command == "armis-untag-device": return_results(untag_device_command(client, args)) elif command == "armis-search-devices": return_results(search_devices_command(client, args)) elif command == "armis-search-devices-by-aql": return_results(search_devices_by_aql_command(client, args)) elif command == "armis-search-alerts-by-aql-string": return_results(search_alerts_by_aql_command(client, args)) # Log exceptions except Exception as e: return_error(f"Failed to execute {command} command. Error: {str(e)}") if __name__ in ("__main__", "__builtin__", "builtins"): main()