McAfee ESM v2
This integration runs queries and receives alarms from McAfee Enterprise Security Manager (ESM). Supports version 10 and above.
Analytics & SIEM · McAfee ESM
Details
| ID | McAfee ESM v2 |
|---|---|
| Provider | Trellix |
| Category | Analytics & SIEM |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Run queries and receive alarms from Intel Security ESM.
This integration was integrated and tested with version 11.3 of McAfee ESM v2.
Previous versions have been declared EOL by the vendor.
Configure McAfee ESM v2 in Cortex
| Parameter | Description | Required |
|---|---|---|
| url | Base URL (e.g. https://example.com) | True |
| credentials | Username | True |
| version | Version: (one of 10.0, 10.1, 10.2, 10.3, 11.1, 11.3) | True |
| isFetch | Fetch incidents | False |
| incidentType | Incident type | False |
| fetchType | Fetch Types: cases, alarms, both (relevant only for fetch incident mode) | False |
| startingFetchID | Start fetch after ID: (relevant only for fetch incident mode) | False |
| fetchLimitCases | Fetch cases limit | False |
| fetchTime | First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days, 3 months, 1 year) | False |
| fetchLimitAlarms | Fetch alarms limit | False |
| timezone | McAfee ESM Timezone in hours (e.g if ESM timezone is +0300 => then insert 3) | False |
| insecure | Trust any certificate (not secure) | False |
| proxy | Use system proxy settings | False |
Required Permissions
| Component | Permission |
|---|---|
| Alarms | Alarm Management and View Data |
| Cases | Incident Management Administrator and Incident Management User |
| Watchlists | Watchlists |
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.
esm-fetch-fields
Gets all fields that can be used in query filters, including type information for each field
Base Command
esm-fetch-fields
Input
There are no input arguments for this command.
Context Output
There is no context output for this command.
Command Example
#### Human Readable Output
>### Fields
>
>|name|types|
>|---|---|
>| AppID | STRING |
>| CommandID | STRING |
>| DomainID | STRING |
>| HostID | STRING |
>| ObjectID | STRING |
>| UserIDDst | STRING |
>| UserIDSrc | STRING |
>| URL | SSTRING |
>| Database_Name | STRING |
>| Message_Text | SSTRING |
>| Response_Time | UINT32 |
>| Application_Protocol | STRING |
>| Object_Type | STRING |
>| Filename | SSTRING |
>| From | SSTRING |
>| To | SSTRING |
>| Cc | SSTRING |
>| Bcc | SSTRING |
>| Subject | SSTRING |
>| Method | STRING |
>| User_Agent | SSTRING |
>| Cookie | SSTRING |
>| Referer | SSTRING |
>| File_Operation | STRING |
>| File_Operation_Succeeded | STRING |
>| Destination_Filename | SSTRING |
>| User_Nickname | STRING |
>| Contact_Name | STRING |
>| Contact_Nickname | STRING |
>| Client_Version | SSTRING |
>| Job_Name | SSTRING |
>| Language | SSTRING |
>| SWF_URL | SSTRING |
>| TC_URL | SSTRING |
>| RTMP_Application | SSTRING |
>| Version | SSTRING |
>| Local_User_Name | SSTRING |
>| NAT_Details | UINT16,IPV4 |
>| Network_Layer | SIGID |
>| Transport_Layer | SIGID |
>| Session_Layer | SIGID |
>| Application_Layer | SIGID |
>| HTTP_Layer | SIGID |
>| HTTP_Req_URL | SSTRING |
>| HTTP_Req_Cookie | SSTRING |
>| HTTP_Req_Referer | SSTRING |
>| HTTP_Req_Host | SSTRING |
>| HTTP_Req_Method | SSTRING |
>| HTTP_User_Agent | SSTRING |
>| DNS_Name | SSTRING |
>| DNS_Type | STRING |
>| DNS_Class | STRING |
>| Query_Response | STRING |
>| Authoritative_Answer | STRING |
>| SNMP_Operation | STRING |
>| SNMP_Item_Type | STRING |
>| SNMP_Version | STRING |
>| SNMP_Error_Code | STRING |
>| NTP_Client_Mode | STRING |
>| NTP_Server_Mode | STRING |
>| NTP_Request | STRING |
>| NTP_Opcode | STRING |
>| SNMP_Item | SSTRING |
>| Interface | STRING |
>| Direction | STRING |
>| Sensor_Name | STRING |
>| Sensor_UUID | SSTRING |
>| Sensor_Type | STRING |
>| Signature_Name | SSTRING |
>| Threat_Name | SSTRING |
>| Destination_Hostname | SSTRING |
>| Category | SSTRING |
>| Process_Name | SSTRING |
>| Grid_Master_IP | IP |
>| Response_Code | STRING |
>| Device_Port | UINT64 |
>| Device_IP | IP |
>| PID | UINT64 |
>| Target_Context | SSTRING |
>| Source_Context | SSTRING |
>| Target_Class | SSTRING |
>| Policy_Name | SSTRING |
>| Destination_Zone | SSTRING |
>| Source_Zone | SSTRING |
>| Queue_ID | STRLIT |
>| Delivery_ID | SSTRING |
>| Recipient_ID | SSTRING |
>| Spam_Score | FLOAT |
>| Mail_ID | SSTRING |
>| To_Address | SSTRING |
>| From_Address | SSTRING |
>| Message_ID | SSTRING |
>| Request_Type | SSTRING |
>| SQL_Statement | SSTRING |
>| External_EventID | UINT64 |
>| Event_Class | SSTRING |
>| Description | SSTRING |
>| File_Hash | GUID |
>| Mainframe_Job_Name | SSTRING |
>| External_SubEventID | UINT64 |
>| Destination_UserID | SSTRING |
>| Source_UserID | SSTRING |
>| Volume_ID | SSTRING |
>| Step_Name | SSTRING |
>| Step_Count | SSTRING |
>| LPAR_DB2_Subsystem | SSTRING |
>| Logical_Unit_Name | SSTRING |
>| Job_Type | SSTRING |
>| FTP_Command | SSTRING |
>| File_Type | SSTRING |
>| DB2_Plan_Name | SSTRING |
>| Catalog_Name | SSTRING |
>| Access_Resource | SSTRING |
>| Table_Name | SSTRING |
>| External_DB2_Server | SSTRING |
>| External_Application | SSTRING |
>| Creator_Name | SSTRING |
>| Return_Code | STRING |
>| Database_ID | SSTRING |
>| Incoming_ID | SSTRING |
>| Handle_ID | UINT64 |
>| Destination_Network | SSTRING |
>| Source_Network | SSTRING |
>| Malware_Insp_Result | SSTRING |
>| Malware_Insp_Action | SSTRING |
>| External_Hostname | SSTRING |
>| Privileged_User | SSTRING |
>| Facility | SSTRING |
>| Area | SSTRING |
>| Instance_GUID | GUID |
>| Logon_Type | SSTRING |
>| Operating_System | SSTRING |
>| File_Path | SSTRING |
>| Agent_GUID | GUID |
>| Reputation | UINT64 |
>| URL_Category | SSTRING |
>| Session_Status | SSTRING |
>| Destination_Logon_ID | SSTRING |
>| Source_Logon_ID | SSTRING |
>| UUID | GUID |
>| External_SessionID | SSTRING |
>| Management_Server | SSTRING |
>| Detection_Method | SSTRING |
>| Target_Process_Name | SSTRING |
>| Analyzer_DAT_Version | FLOAT |
>| Forwarding_Status | SSTRING |
>| Reason | SSTRING |
>| Threat_Handled | SSTRING |
>| Threat_Category | SSTRING |
>| Device_Action | SSTRING |
>| Database_GUID | GUID |
>| SQL_Command | SSTRING |
>| Destination_Directory | SSTRING |
>| Directory | SSTRING |
>| Mailbox | SSTRING |
>| Handheld_ID | UINT64 |
>| Policy_ID | UINT64 |
>| Server_ID | UINT64 |
>| Registry_Value | SSTRING |
>| Registry_Key | SSTRING |
>| Caller_Process | SSTRING |
>| DAT_Version | FLOAT |
>| Interface_Dest | SSTRING |
>| Datacenter_Name | SSTRING |
>| Datacenter_ID | SSTRING |
>| Virtual_Machine_ID | SSTRING |
>| Virtual_Machine_Name | SSTRING |
>| PCAP_Name | SSTRING |
>| Search_Query | SSTRING |
>| Service_Name | SSTRING |
>| External_Device_Name | SSTRING |
>| External_Device_ID | SSTRING |
>| External_Device_Type | SSTRING |
>| Organizational_Unit | SSTRING |
>| Privileges | SSTRING |
>| Reputation_Name | SSTRING |
>| Vulnerability_References | SSTRING |
>| Web_Domain | SSTRING |
>| Sub_Status | SSTRING |
>| Status | SSTRING |
>| Access_Privileges | SSTRING |
>| Rule_Name | SSTRING |
>| App_Layer_Protocol | SSTRING |
>| Group_Name | SSTRING |
>| Authentication_Type | SSTRING |
>| New_Value | SSTRING |
>| Old_Value | SSTRING |
>| Security_ID | SSTRING |
>| SHA1 | SSTRING |
>| Reputation_Score | FLOAT |
>| Parent_File_Hash | GUID |
>| File_ID | SSTRING |
>| Engine_List | SSTRING |
>| Device_URL | SSTRING |
>| Attacker_IP | IPV4 |
>| Victim_IP | IPV4 |
>| Incident_ID | INT64 |
>| Attribute_Type | SSTRING |
>| Access_Mask | SSTRING |
>| Object_GUID | GUID |
>| VPN_Feature_Name | SSTRING |
>| Reputation_Server_IP | IP |
>| DNS_Server_IP | IP |
>| Hash_Type | SSTRING |
>| Hash | SSTRING |
>| Subcategory | SSTRING |
>| Wireless_SSID | SSTRING |
>| Share_Name | SSTRING |
>| CnC_Host | SSTRING |
>| Device_Confidence | UINT64 |
>| SHA256 | SSTRING |
>| AppID | STRING |
>| CommandID | STRING |
>| DSIDSigID | SIGID |
>| Action | UINT8 |
>| ASNGeoDst | UINT64 |
>| DSID | UINT64 |
>| ZoneDst | UINT16 |
>| SigID | SIGID |
>| GUIDSrc | GUID |
>| NDDevIDSrc | UINT16 |
>| ID | UINT64 |
>| Protocol | UINT8 |
>| NormID | UINT32 |
>| ZoneSrc | UINT16 |
>| FirstTime | UINT32 |
>| SrcPort | UINT16 |
>| AvgSeverity | FLOAT |
>| DstPort | UINT16 |
>| SrcIP | IP |
>| GUIDDst | GUID |
>| DstIP | IP |
>| NDDevIDDst | UINT16 |
>| SrcMac | MAC_ADDRESS |
>| SessionID | UINT64 |
>| ASNGeoSrc | UINT64 |
>| DstMac | MAC_ADDRESS |
>| LastTime | UINT32 |
### esm-search
***
Perform a query against Mcafee ESM SIEM
#### Base Command
`esm-search`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| timeRange | The time period for the search. Can be LAST_3_DAYS, LAST_2_DAYS, LAST_24_HOURS, PREVIOUS_DAY, CURRENT_DAY, LAST_HOUR, LAST_30_MINUTES, LAST_10_MINUTES, LAST_MINUTE, CUSTOM, PREVIOUS_YEAR, CURRENT_YEAR, PREVIOUS_QUARTER, CURRENT_QUARTER, PREVIOUS_MONTH, CURRENT_MONTH, PREVIOUS_WEEK, or CURRENT_WEEK. | Optional |
| filters | Filter on the query results, should be a JSON string, of the format EsmFilter (read more on that here - https://<esm-ip>:<esm-port>/rs/esm/help/types/EsmFilter) | Required |
| queryType | Type of query to run. Can be "EVENT", "FLOW", or "ASSETS". Default is "EVENT". | Optional |
| timeOut | Maximum time to wait before timeout (in minutes). Default is 30. | Optional |
| customStart | If the timeRange argument is set to CUSTOM, the start time for the time range. For example: 2017-06-01T12:48:16.734Z | Optional |
| customEnd | If the timeRange argument is set to CUSTOM, the end time for the time range. For example: 2017-06-01T12:48:16.734Z | Optional |
| fields | The fields that will be selected when this query is executed. | Optional |
| limit | Query results can be limited to a maximum row count. | Optional |
#### Context Output
There is no context output for this command.
#### Command Example
```!esm-search timeRange="CURRENT_YEAR" filters="[{\"type\":\"EsmFieldFilter\",\"field\":{\"name\":\"SrcIP\"},\"operator\":\"IN\"}]" limit="3"```
#### Context Example
{
“McAfeeESM”: {
“results”: [
{
“ActionName”: “success”,
“AlertDstIP”: “192.168.1.111”,
“AlertDstPort”: “0”,
“AlertIPSIDAlertID”: “144115188075855872|779674”,
“AlertLastTime”: “2020-01-01T05:48:20Z”,
“AlertProtocol”: “n/a”,
“AlertSrcIP”: “22.22.22.22”,
“AlertSrcPort”: “0”
},
{
“ActionName”: “success”,
“AlertDstIP”: “192.168.1.111”,
“AlertDstPort”: “0”,
“AlertIPSIDAlertID”: “144115188075855872|779675”,
“AlertLastTime”: “2020-01-01T05:48:22Z”,
“AlertProtocol”: “n/a”,
“AlertSrcIP”: “22.22.22.22”,
“AlertSrcPort”: “0”
},
{
“ActionName”: “success”,
“AlertDstIP”: “192.168.1.111”,
“AlertDstPort”: “0”,
“AlertIPSIDAlertID”: “144115188075855872|779676”,
“AlertLastTime”: “2020-01-01T10:51:57Z”,
“AlertProtocol”: “n/a”,
“AlertSrcIP”: “33.33.33.33”,
“AlertSrcPort”: “0”
}
]
}
}
#### Human Readable Output
>Search results
>
>|Alert.IPSIDAlertID|Alert.SrcIP|Alert.SrcPort|Alert.DstIP|Alert.DstPort|Alert.Protocol|Alert.LastTime|Action.Name|
>|--|--|--|--|--|--|--|--|
>| 144115188075855872\|779674|22.22.22.22|0|192.168.1.111|0|n/a|2020-01-01T05:48:20Z|success |
>| 144115188075855872\|779675|22.22.22.22|0|192.168.1.111|0|n/a|2020-01-01T05:48:22Z|success |
>| 144115188075855872\|779676|33.33.33.33|0|192.168.1.111|0|n/a|2020-01-01T10:51:57Z|success |
### esm-fetch-alarms
***
Retrieves a list of triggered alarms.
#### Base Command
`esm-fetch-alarms`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| timeRange | The time period for the search. Can be LAST_3_DAYS, LAST_2_DAYS, LAST_24_HOURS, PREVIOUS_DAY, CURRENT_DAY, LAST_HOUR, LAST_30_MINUTES, LAST_10_MINUTES, LAST_MINUTE, CUSTOM, PREVIOUS_YEAR, CURRENT_YEAR, PREVIOUS_QUARTER, CURRENT_QUARTER, PREVIOUS_MONTH, CURRENT_MONTH, PREVIOUS_WEEK, or CURRENT_WEEK. | Optional |
| customStart | If the timeRange argument is set to CUSTOM, the start time for the time range. For example: 2017-06-01T12:48:16.734Z | Optional |
| customEnd | If the timeRange argument is set to CUSTOM, the end time for the time range. For example: 2017-06-01T12:48:16.734Z | Optional |
| assignedUser | User assigned to handle the triggered alarm. Use the 'ME' option to use the instance user, or use [format EsmUser](https://<esm-ip>:<esm-port>/rs/esm/help/types/EsmUser). | Optional |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.Alarm.ID | number | Alarm ID. |
| McAfeeESM.Alarm.summary | string | Alarm summary. |
| McAfeeESM.Alarm.assignee | string | Alarm assignee. |
| McAfeeESM.Alarm.severity | number | Alarm severity. |
| McAfeeESM.Alarm.triggeredDate | date | Alarm triggered date. |
| McAfeeESM.Alarm.acknowledgedDate | date | Alarm acknowledged date. |
| McAfeeESM.Alarm.acknowledgedUsername | string | Alarm acknowledged username. |
| McAfeeESM.Alarm.alarmName | string | Alarm name. |
| McAfeeESM.Alarm.conditionType | number | Alarm condition type. |
#### Command Example
```!esm-fetch-alarms timeRange=CURRENT_MONTH```
#### Context Example
{
“McAfeeESM”: {
“Alarm”: [
{
“ID”: 42710,
“acknowledgedDate”: “”,
“acknowledgedUsername”: “”,
“alarmName”: “Alarm Test”,
“assignee”: “ANALYST”,
“conditionType”: 22,
“severity”: 50,
“summary”: “Event rate exceeded 10 by 17”,
“triggeredDate”: “2020-06-24T13:05:43Z”
},
{
“ID”: 42709,
“acknowledgedDate”: “”,
“acknowledgedUsername”: “”,
“alarmName”: “Alarm Test”,
“assignee”: “ANALYST”,
“conditionType”: 22,
“severity”: 50,
“summary”: “Event rate exceeded 10 by 1”,
“triggeredDate”: “2020-06-24T12:53:12Z”
},
{
“ID”: 42708,
“acknowledgedDate”: “”,
“acknowledgedUsername”: “”,
“alarmName”: “Alarm Test”,
“assignee”: “ANALYST”,
“conditionType”: 22,
“severity”: 50,
“summary”: “Event rate exceeded 10 by 2”,
“triggeredDate”: “2020-06-24T11:32:08Z”
}
]
}
}
#### Human Readable Output
>### Alarms
>
>|id|acknowledgedDate|acknowledgedUsername|alarmName|assignee|conditionType|severity|summary|triggeredDate|
>|---|---|---|---|---|---|---|---|---|
>| 42710 | | | Alarm Test | ANALYST | 22 | 50 | Event rate exceeded 10 by 17 | 2020-06-24T13:05:43Z |
>| 42709 | | | Alarm Test | ANALYST | 22 | 50 | Event rate exceeded 10 by 1 | 2020-06-24T12:53:12Z |
>| 42708 | | | Alarm Test | ANALYST | 22 | 50 | Event rate exceeded 10 by 2 | 2020-06-24T11:32:08Z |
### esm-get-case-list
***
Gets a list of cases from McAfee ESM.
#### Base Command
`esm-get-case-list`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| since | Filters for cases that were opened before this date. In the format "<number><timeunit>", for example: 1 day,30 minutes,2 weeks,6 months,1 year | Optional |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.Case.ID | number | Case ID. |
| McAfeeESM.Case.Summary | string | The summary of the case. |
| McAfeeESM.Case.Status | string | The status of the case. |
| McAfeeESM.Case.OpenTime | date | The date and time when the case was opened. |
| McAfeeESM.Case.Severity | number | The severity of the case. |
#### Command Example
```!esm-get-case-list since="1 month"```
#### Context Example
{
“McAfeeESM”: {
“Case”: [
{
“ID”: 33262,
“OpenTime”: “2020-06-23T06:38:03Z”,
“Severity”: 50,
“Status”: “Open”,
“Summary”: “Signature ID ‘Failed User Logon’ (306-31) match found”
},
{
“ID”: 33261,
“OpenTime”: “2020-06-22T12:04:09Z”,
“Severity”: 50,
“Status”: “Open”,
“Summary”: “Signature ID ‘Failed User Logon’ (306-31) match found”
},
{
“ID”: 33264,
“OpenTime”: “2020-06-23T12:13:08Z”,
“Severity”: 50,
“Status”: “Open”,
“Summary”: “Signature ID ‘Failed User Logon’ (306-31) match found”
}
]
}
}
#### Human Readable Output
>### cases since 1 month
>
>|ID|OpenTime|Severity|Status|Summary|
>|---|---|---|---|---|
>| 33262 | 2020-06-23T06:38:03Z | 50 | Open | Signature ID 'Failed User Logon' (306-31) match found |
>| 33261 | 2020-06-22T12:04:09Z | 50 | Open | Signature ID 'Failed User Logon' (306-31) match found |
>| 33264 | 2020-06-23T12:13:08Z | 50 | Open | Signature ID 'Failed User Logon' (306-31) match found |
### esm-add-case
***
Adds a case to the system.
#### Base Command
`esm-add-case`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| summary | The name of the case. | Required |
| status | The status of the case. Run the esm-get-case-statuses command to view all statuses. | Optional |
| assignee | User assigned to the case. | Optional |
| severity | The severity of the case (1 - 100). | Optional |
| organization | The organization assigned to the case. Run the esm-get-organization-list command to view all organizations. | Optional |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.Case.ID | number | The ID of the case. |
| McAfeeESM.Case.Summary | string | The summary of the case. |
| McAfeeESM.Case.Status | string | The status of the case. |
| McAfeeESM.Case.OpenTime | date | The open time of the case. |
| McAfeeESM.Case.Severity | number | The severity of the case. |
| McAfeeESM.Case.Assignee | string | The assignee of the case. |
| McAfeeESM.Case.Organization | string | The organization of the case. |
| McAfeeESM.Case.EventList | Unknown | List of the case's events. |
| McAfeeESM.Case.Notes | Unknown | List of the case's notes. |
#### Command Example
```!esm-add-case summary="McAfee ESM v2 add case"```
#### Context Example
{
“McAfeeESM”: {
“Case”: {
“Assignee”: “ANALYST”,
“ID”: 33272,
“OpenTime”: “2020-06-24T13:10:01Z”,
“Organization”: “None”,
“Severity”: 1,
“Status”: “Open”,
“Summary”: “McAfee ESM v2 add case”
}
}
}
#### Human Readable Output
>### Case
>
>|Assignee|ID|OpenTime|Organization|Severity|Status|Summary|
>|---|---|---|---|---|---|---|
>| ANALYST | 33272 | 2020-06-24T13:10:01Z | None | 1 | Open | McAfee ESM v2 add case |
### esm-edit-case
***
Edit the details of an existing case.
#### Base Command
`esm-edit-case`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| id | The ID of the case. | Required |
| summary | The name of the case. | Optional |
| severity | The new severity of the case (1 - 100). | Optional |
| assignee | User assigned to the case. | Optional |
| status | The new status of the case. Run the esm-get-case-statuses command to view all statuses. | Optional |
| organization | The organization assigned to the case. Run the esm-get-organization-list command to view all organizations. | Optional |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.Case.ID | number | The ID of the case. |
| McAfeeESM.Case.Summary | string | The summary of the case. |
| McAfeeESM.Case.Status | string | The status of the case. |
| McAfeeESM.Case.OpenTime | date | The open time of the case. |
| McAfeeESM.Case.Severity | number | The severity of the case. |
| McAfeeESM.Case.Assignee | string | The assignee of the case. |
| McAfeeESM.Case.Organization | string | The organization of the case. |
| McAfeeESM.Case.EventList | Unknown | List of the case's events. |
| McAfeeESM.Case.Notes | Unknown | List of the case's notes. |
#### Command Example
```!esm-edit-case id="33266" summary="McAfee ESM v2 edit case"```
#### Context Example
{
“McAfeeESM”: {
“Case”: {
“Assignee”: “ANALYST”,
“ID”: 33266,
“OpenTime”: “2020-06-24T10:54:21Z”,
“Organization”: “None”,
“Severity”: 1,
“Status”: “Open”,
“Summary”: “McAfee ESM v2 edit case”
}
}
}
#### Human Readable Output
>### Case
>
>|Assignee|ID|OpenTime|Organization|Severity|Status|Summary|
>|---|---|---|---|---|---|---|
>| ANALYST | 33266 | 2020-06-24T10:54:21Z | None | 1 | Open | McAfee ESM v2 edit case |
### esm-get-case-statuses
***
Gets a list of valid case statuses from the system.
#### Base Command
`esm-get-case-statuses`
#### Input
There are no input arguments for this command.
#### Context Output
There is no context output for this command.
#### Command Example
```!esm-get-case-statuses```
#### Human Readable Output
>### case statuses
>
>|id|name|default|showInCasePane|
>|---|---|---|---|
>| 2 | Closed | false | false |
>| 11830 | McAfee_ESM_v2_add_case | false | false |
>| 1 | Open | true | true |
>| 11725 | Research_1563355610148 | false | true |
>| 11825 | TestMcAfee_ESM_v2 | false | false |
>| 11758 | bbbb | false | false |
>| 11776 | test | false | true |
>| 11777 | test1 | false | false |
>| 11268 | test2 | false | true |
>| 11267 | test3 | false | true |
>| 11890 | test_delete_case | false | false |
>| 11889 | test_edit_case | false | false |
### esm-edit-case-status
***
Edits the status of a case.
#### Base Command
`esm-edit-case-status`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| original_name | The name of the case status to edit. | Required |
| new_name | The new name for the case status. | Required |
| show_in_case_pane | Whether the status will display in the case pane. Can be "True" or "False". Default is "True". | Optional |
#### Context Output
There is no context output for this command.
#### Command Example
```!esm-edit-case-status original_name=test_edit_case new_name=edited_case```
#### Human Readable Output
>
>Edited case status with ID: 11889
### esm-get-case-detail
***
Gets the details of an existing case.
#### Base Command
`esm-get-case-detail`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| id | The ID of the case. | Required |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.Case.ID | number | The ID of the case. |
| McAfeeESM.Case.Summary | string | The summary of the case. |
| McAfeeESM.Case.Status | string | The status of the case. |
| McAfeeESM.Case.OpenTime | date | The open time of the case. |
| McAfeeESM.Case.Severity | number | The severity of the case. |
| McAfeeESM.Case.Assignee | string | The assignee of the case. |
| McAfeeESM.Case.Organization | string | The organization of the case. |
| McAfeeESM.Case.EventList | Unknown | List of the case's events. |
| McAfeeESM.Case.Notes | Unknown | List of the case's notes. |
#### Command Example
```!esm-get-case-detail id="33264"```
#### Context Example
{
“McAfeeESM”: {
“Case”: {
“Assignee”: “ANALYST”,
“ID”: 33264,
“OpenTime”: “2020-06-23T12:13:08Z”,
“Organization”: “None”,
“Severity”: 50,
“Status”: “Open”,
“Summary”: “Signature ID ‘Failed User Logon’ (306-31) match found”
}
}
}
#### Human Readable Output
>### Case
>
>|Assignee|ID|OpenTime|Organization|Severity|Status|Summary|
>|---|---|---|---|---|---|---|
>| ANALYST | 33264 | 2020-06-23T12:13:08Z | None | 50 | Open | Signature ID 'Failed User Logon' (306-31) match found |
### esm-get-case-event-list
***
Gets case event details.
#### Base Command
`esm-get-case-event-list`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| ids | Comma-separated list of event IDs. | Required |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.CaseEvent.ID | string | The ID of the event. |
| McAfeeESM.CaseEvent.LastTime | date | The time the event was last updated. |
| McAfeeESM.CaseEvent.Message | string | The message of the event. |
#### Command Example
```!esm-get-case-event-list ids="42687"```
### esm-add-case-status
***
Adds a status to the specified case.
#### Base Command
`esm-add-case-status`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| name | The name of the case status. | Required |
| show_in_case_pane | Whether the status will display in the case pane. Can be "True" or "False". Default is "True". | Optional |
#### Context Output
There is no context output for this command.
#### Command Example
```!esm-add-case-status name=test_add_case```
#### Human Readable Output
>Added case status : test_add_case
### esm-delete-case-status
***
Deletes the status of a case.
#### Base Command
`esm-delete-case-status`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| name | The name of the case status to delete. | Required |
#### Context Output
There is no context output for this command.
#### Command Example
```!esm-delete-case-status name=test_delete_case```
#### Human Readable Output
>Deleted case status with ID: 11890
### esm-get-organization-list
***
Gets a case organization.
#### Base Command
`esm-get-organization-list`
#### Input
There are no input arguments for this command.
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.Organization.ID | number | Organization ID. |
| McAfeeESM.Organization.Name | string | Organization name. |
#### Command Example
```!esm-get-organization-list```
#### Context Example
{
“McAfeeESM”: {
“Organization”: [
{
“ID”: 2,
“Name”: “ABC”
},
{
“ID”: 1,
“Name”: “Org”
}
]
}
}
#### Human Readable Output
>### Organizations
>
>|id|name|
>|---|---|
>| 2 | ABC |
>| 1 | Org |
### esm-get-user-list
***
Gets a list of all users.
#### Base Command
`esm-get-user-list`
#### Input
There are no input arguments for this command.
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.User.ID | number | The ID of the user. |
| McAfeeESM.User.Name | string | The ESM user name. |
| McAfeeESM.User.Email | string | The email address of the user. |
| McAfeeESM.User.SMS | string | The SMS details of the user. |
| McAfeeESM.User.IsMaster | boolean | Whether the user is a master user. |
| McAfeeESM.User.IsAdmin | boolean | Whether the user is an admin. |
#### Command Example
```!esm-get-user-list```
#### Context Example
{
“McAfeeESM”: {
“User”: [
{
“Email”: “”,
“Groups”: “[]”,
“ID”: 6,
“IsAdmin”: false,
“IsMaster”: false,
“Name”: “abcd”,
“SMS”: “”
},
{
“Email”: “”,
“Groups”: “[1, 2]”,
“ID”: 7,
“IsAdmin”: true,
“IsMaster”: true,
“Name”: “gavrieltest”,
“SMS”: “”
},
{
“Email”: “”,
“Groups”: “[2]”,
“ID”: 1,
“IsAdmin”: false,
“IsMaster”: true,
“Name”: “ANALYST”,
“SMS”: “”
}
]
}
}
#### Human Readable Output
>### User list
>
>|ID|Name|Email|Groups|IsMaster|IsAdmin|SMS|
>|---|---|---|---|---|---|---|
>| 6 | abcd | | [] | false | false | |
>| 7 | gavrieltest | | [1, 2] | true | true | |
>| 1 | ANALYST | | [2] | true | false | |
### esm-acknowledge-alarms
***
Marks triggered alarms as acknowledged.
#### Base Command
`esm-acknowledge-alarms`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| alarmIds | Comma-separated list of triggered alarm IDs to be marked as acknowledged. | Required |
#### Context Output
There is no context output for this command.
#### Command Example
```!esm-acknowledge-alarms alarmIds="42710"```
#### Human Readable Output
>Alarms has been Acknowledged.
### esm-unacknowledge-alarms
***
Marks triggered alarms as unacknowledged.
#### Base Command
`esm-unacknowledge-alarms`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| alarmIds | Comma-separated list of triggered alarm IDs to be marked as unacknowledged. | Required |
#### Context Output
There is no context output for this command.
#### Command Example
```!esm-unacknowledge-alarms alarmIds="42687"```
#### Human Readable Output
>Alarms has been Unacknowledged.
### esm-delete-alarms
***
Deletes triggered alarms.
#### Base Command
`esm-delete-alarms`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| alarmIds | Comma-separated list of triggered alarm IDs to delete. | Required |
#### Context Output
There is no context output for this command.
#### Command Example
```!esm-delete-alarms alarmIds="42709"```
#### Human Readable Output
>Alarms has been Deleted.
### esm-get-alarm-event-details
***
Gets the details for the triggered alarm.
#### Base Command
`esm-get-alarm-event-details`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| eventId | The event for which to get the details. Run the esm-list-alarm-events command to get the ID. | Required |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.AlarmEvent.ID | string | Event ID. |
| McAfeeESM.AlarmEvent.SubType | string | Event type. |
| McAfeeESM.AlarmEvent.Severity | number | Event severity. |
| McAfeeESM.AlarmEvent.Message | string | Event message. |
| McAfeeESM.AlarmEvent.LastTime | date | Event time. |
| McAfeeESM.AlarmEvent.SrcIP | string | Source IP of the event. |
| McAfeeESM.AlarmEvent.DstIP | string | Destination IP of the event. |
| McAfeeESM.AlarmEvent.Cases | Unknown | A list of cases related to the event. |
| McAfeeESM.AlarmEvent.Cases.ID | string | Case ID. |
| McAfeeESM.AlarmEvent.Cases.OpenTime | date | Case creation time. |
| McAfeeESM.AlarmEvent.Cases.Severity | number | Case severity. |
| McAfeeESM.AlarmEvent.Cases.Status | string | Case status. |
| McAfeeESM.AlarmEvent.Cases.Summary | string | Case summary. |
| McAfeeESM.AlarmEvent.DstMac | string | Destination MAC address of the event. |
| McAfeeESM.AlarmEvent.SrcMac | string | Source MAC address of the event. |
| McAfeeESM.AlarmEvent.DstPort | string | Destination port of the event. |
| McAfeeESM.AlarmEvent.SrcPort | string | Source port of the event. |
| McAfeeESM.AlarmEvent.FirstTime | date | The first time for the event. |
| McAfeeESM.AlarmEvent.NormalizedDescription | string | Normalized description of the event. |
#### Command Example
```!esm-get-alarm-event-details eventId=144115188075855872|802641```
#### Context Example
{
“McAfeeESM”: {
“AlarmEvent”: {
“Case”: [
{
“ID”: 33260,
“OpenTime”: “2020-06-22T06:16:24Z”,
“Severity”: 50,
“Status”: “Open”,
“Summary”: “Signature ID ‘Failed User Logon’ (306-31) match found”
}
],
“DstIP”: “192.168.1.111”,
“DstMac”: “00:00:00:00:00:00”,
“DstPort”: “0”,
“FirstTime”: “2020-06-22T06:16:05Z”,
“ID”: 802641,
“LastTime”: “2020-06-22T06:16:05Z”,
“Message”: “Failed User Logon”,
“NormalizedDescription”: “The Login category indicates events related to logging in to hosts or services. Belongs to Authentication: The authentication category indicates events relating to system access.”,
“Severity”: 25,
“SrcIP”: “44.44.44.44”,
“SrcMac”: “00:00:00:00:00:00”,
“SrcPort”: “0”,
“SubType”: “failure”
}
}
}
#### Human Readable Output
>### Alarm events
>
>|Case|DstIP|DstMac|DstPort|FirstTime|ID|LastTime|Message|NormalizedDescription|Severity|SrcIP|SrcMac|SrcPort|SubType|
>|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
>| {'ID': 33260, 'OpenTime': '2020-06-22T06:16:24Z', 'Severity': 50, 'Status': 'Open', 'Summary': "Signature ID 'Failed User Logon' (306-31) match found"} | 192.168.1.111 | 00:00:00:00:00:00 | 0 | 2020-06-22T06:16:05Z | 802641 | 2020-06-22T06:16:05Z | Failed User Logon | The Login category indicates events related to logging in to hosts or services. Belongs to Authentication: The authentication category indicates events relating to system access. | 25 | 44.44.44.44 | 00:00:00:00:00:00 | 0 | failure |
### esm-list-alarm-events
***
Gets a list of events related to the alarm.
#### Base Command
`esm-list-alarm-events`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| alarmId | The alarm for which to get the details. Run the esm-fetch-alarms command to get the ID. | Required |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.AlarmEvent.ID | string | Event ID. |
| McAfeeESM.AlarmEvent.SubType | string | Event type. |
| McAfeeESM.AlarmEvent.Severity | number | Event severity. |
| McAfeeESM.AlarmEvent.Message | string | Event message. |
| McAfeeESM.AlarmEvent.LastTime | date | Event time. |
| McAfeeESM.AlarmEvent.SrcIP | string | Source IP of the event. |
| McAfeeESM.AlarmEvent.DstIP | string | Destination IP of the event. |
| McAfeeESM.AlarmEvent.Cases | Unknown | A list of cases related to the event. |
| McAfeeESM.AlarmEvent.Cases.ID | string | Case ID. |
| McAfeeESM.AlarmEvent.Cases.OpenTime | date | Case creation time. |
| McAfeeESM.AlarmEvent.Cases.Severity | number | Case severity. |
| McAfeeESM.AlarmEvent.Cases.Status | string | Case status. |
| McAfeeESM.AlarmEvent.Cases.Summary | string | Case summary. |
#### Command Example
```!esm-list-alarm-events alarmId=42687```
#### Context Example
{
“McAfeeESM”: {
“AlarmEvent”: {
“DstIP”: “192.168.1.111”,
“DstMac”: null,
“DstPort”: null,
“FirstTime”: null,
“ID”: “144115188075855872|802641”,
“LastTime”: “2020-06-22T06:16:05Z”,
“Message”: “Failed User Logon”,
“NormalizedDescription”: null,
“Severity”: 25,
“SrcIP”: “11.11.11.11”,
“SrcMac”: null,
“SrcPort”: null,
“SubType”: “failure”
}
}
}
#### Human Readable Output
>### Alarm events
>
>|DstIP|DstMac|DstPort|FirstTime|ID|LastTime|Message|NormalizedDescription|Severity|SrcIP|SrcMac|SrcPort|SubType|
>|---|---|---|---|---|---|---|---|---|---|---|---|---|
>| 192.168.1.111 | | | | 144115188075855872\|802641 | 2020-06-22T06:16:05Z | Failed User Logon | | 25 | 11.11.11.11 | | | failure |
### esm-create-watchlist
***
Create a new watchlist.
#### Base Command
`esm-create-watchlist`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| name | The new watchlist name. | Required |
| type | The type of the new watchlist. | Required |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.Watchlist.name | string | The watchlist name |
| McAfeeESM.Watchlist.id | number | The watchlist id |
| McAfeeESM.Watchlist.type | string | The watchlist type |
#### Command Example
```!esm-create-watchlist name=test_watchlist type=IPAddress```
#### Context Example
{
“McAfeeESM”: {
“Watchlist”: {
“id”: 54,
“name”: “test_watchlist”,
“type”: “IPAddress”
}
}
}
#### Human Readable Output
>Watchlist test_watchlist created.
### esm-delete-watchlist
***
Delete a watchlist.
#### Base Command
`esm-delete-watchlist`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| ids | the watch list ids to delete. | Optional |
| names | the watch list names to delete. | Optional |
#### Context Output
There is no context output for this command.
#### Command Example
```!esm-delete-watchlist names=test_watchlist```
#### Human Readable Output
>Watchlists removed
### esm-watchlist-add-entry
***
Create a new watchlist entry.
#### Base Command
`esm-watchlist-add-entry`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| watchlist_name | The watchlist name. | Optional |
| watchlist_id | The watchlist id. | Optional |
| values | The values you want to add to watchlist. (CSV format) | Required |
#### Context Output
There is no context output for this command.
#### Command Example
```!esm-watchlist-add-entry watchlist_name=test_watchlist values=1.1.1.1,2.2.2.2```
#### Human Readable Output
>Watchlist successfully updated.
### esm-watchlist-delete-entry
***
Delete watchlist entry.
#### Base Command
`esm-watchlist-delete-entry`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| watchlist_name | The watchlist name. | Optional |
| watchlist_id | The watchlist id. | Optional |
| values | The values you want to remove from watchlist. (CSV format) | Required |
#### Context Output
There is no context output for this command.
#### Command Example
```!esm-watchlist-delete-entry watchlist_name=test_watchlist values=1.1.1.1,2.2.2.2```
#### Human Readable Output
>Watchlist successfully updated.
### esm-watchlist-list-entries
***
Get watchlist entries.
#### Base Command
`esm-watchlist-list-entries`
#### Input
| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| watchlist_name | The watchlist name. | Optional |
| watchlist_id | The watchlist id. | Optional |
| limit | max count of values. | Required |
| offset | values offset. | Required |
#### Context Output
| **Path** | **Type** | **Description** |
| --- | --- | --- |
| McAfeeESM.Watchlist.data | Unknown | The watchlist data |
| McAfeeESM.Watchlist.name | string | The watchlist name |
#### Command Example
```!esm-watchlist-list-entries watchlist_name=test_watchlist```
#### Context Example
{
“McAfeeESM”: {
“Watchlist”: {
“data”: [
“1.1.1.1”,
“2.2.2.2”
],
“name”: “test_watchlist”
}
}
}
```
Human Readable Output
results from test_watchlist watchlist
data 1.1.1.1,
2.2.2.2,
esm-get-watchlists
Returns a list of watchlists’ names and IDs.
Base Command
esm-get-watchlists
Input
| Argument Name | Description | Required |
|---|---|---|
| hidden | Whether to include hidden watchlists. Can be true or false. Possible values are: true, false. Default is true. | Required |
| dynamic | Whether to include dynamic watchlists. Can be true or false. Possible values are: true, false. Default is true. | Required |
| write_only | Whether to include write only watchlists. Can be true or false. Possible values are: true, false. Default is false. | Required |
| indexed_only | Whether to include indexed only watchlists. Can be true or false. Possible values are: true, false. Default is false. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| McAfeeESM.Watchlist.name | string | The name of the watchlist. |
| McAfeeESM.Watchlist.id | number | The ID of the watchlist. |
| McAfeeESM.Watchlist.type | string | The type of the watchlist. |
Configuration parameters
url— Base URL (e.g. https://example.com) (required)credentials— Username (required)version— Version: (one of 10.0, 10.1, 10.2, 10.3, 11.1, 11.3) (required)isFetch— Fetch incidentsincidentType— Incident typeincidentFetchInterval— Incidents Fetch IntervalfetchType— Fetch Types: cases, alarms, both (relevant only for fetch incident mode)startingFetchID— Start fetch after ID: (relevant only for fetch incident mode)fetchLimitCases— Fetch cases limitfetchTime— First fetch timestamp (<number> <time unit>, e.g., 12 hours, 7 days, 3 months, 1 year)fetchLimitAlarms— Fetch alarms limittimezone— McAfee ESM Timezone in hours (e.g if ESM timezone is +0300 => then insert 3)insecure— Trust any certificate (not secure)proxy— Use system proxy settings
Commands (25)
-
esm-acknowledge-alarmsMarks triggered alarms, as acknowledged.
-
esm-add-caseAdds a case to the system.
-
esm-add-case-statusAdds a status to the specified case.
-
esm-create-watchlistCreates a new watchlist.
-
esm-delete-alarmsDeletes triggered alarms.
-
esm-delete-case-statusDeletes the status of a case.
-
esm-delete-watchlistDeletes a watchlist.
-
esm-edit-caseEdit the details of an existing case.
-
esm-edit-case-statusEdits the status of a case.
-
esm-fetch-alarmsRetrieves a list of triggered alarms.
-
esm-fetch-fieldsReturns all fields that can be used in query filters, including type information for each field.
-
esm-get-alarm-event-detailsGets the details for the triggered alarm.
-
esm-get-case-detailReturns the details of an existing case.
-
esm-get-case-event-listReturns case event details.
-
esm-get-case-listReturns a list of cases from McAfee ESM.
-
esm-get-case-statusesReturns a list of valid case statuses from the system.
-
esm-get-organization-listReturns a case organization.
-
esm-get-user-listReturns a list of all users.
-
esm-get-watchlistsReturns a list of watchlists' names and IDs.
-
esm-list-alarm-eventsGets a list of events related to the alarm.
-
esm-searchPerform a query against McAfee ESM SIEM.
-
esm-unacknowledge-alarmsMarks triggered alarms, as unacknowledged.
-
esm-watchlist-add-entryCreates a new watchlist entry.
-
esm-watchlist-delete-entryDeletes a watchlist entry.
-
esm-watchlist-list-entriesReturns a list of watchlist entries.
import itertools import time from collections.abc import Callable from datetime import datetime, timedelta import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 from urllib3 import disable_warnings disable_warnings() CONTEXT_INTEGRATION_NAME = "McAfeeESM." class EmptyFile(Exception): pass class McAfeeESMClient(BaseClient): demisto_format = "%Y-%m-%dT%H:%M:%SZ" def __init__(self, params: dict): self.args = demisto.args() self.__user_name = params.get("credentials", {}).get("identifier", "") self.__password = params.get("credentials", {}).get("password", "") self.difference = int(params.get("timezone", 0)) self.version = params.get("version", "10.2") super().__init__( "{}/rs/esm/v2/".format(params.get("url", "").strip("/")), proxy=params.get("proxy", False), verify=not params.get("insecure", False), ) self._headers = {"Content-Type": "application/json"} self.__login() self.__cache: dict = {"users": [], "org": [], "status": []} def __del__(self): self.__logout() super().__del__() def _is_status_code_valid(self, *_other): # noqa return True def __request(self, mcafee_command: str, data: Union[str, dict] = None, params: dict = None): if data: data = json.dumps(data) result = self._http_request("POST", mcafee_command, data=data, params=params, resp_type="request", timeout=60) if result.ok: if result.content: return result.json() else: return {} else: raise DemistoException(f"{mcafee_command} failed with error[{result.content.decode()}].") def __login(self): params = { "username": base64.b64encode(self.__user_name.encode("ascii")).decode(), "password": base64.b64encode(self.__password.encode("ascii")).decode(), "locale": "en_US", } res = self._http_request("POST", "login", data=json.dumps(params), resp_type="response", timeout=20) self._headers["Cookie"] = "JWTToken={}".format(res.cookies.get("JWTToken")) self._headers["X-Xsrf-Token"] = res.headers.get("Xsrf-Token") if None in (self._headers["X-Xsrf-Token"], self._headers["Cookie"]): raise DemistoException( f"Failed login\nurl: {self._base_url}login\nresponse status: {res.status_code}\nresponse: {res.text}\n" ) def __logout(self): self._http_request("DELETE", "logout", resp_type="response") def test_module(self) -> tuple[str, dict, str]: params = demisto.params() # check credentials self.get_organization_list(raw=True) # check fetch parameters if params.get("isFetch"): start_id = params.get("startingFetchID", "0") if not start_id.isdigit(): raise DemistoException(f'Invalid startingFetchID value. Expected: numeric value, Received "{start_id}"') return "ok", {}, "ok" def __username_and_id(self, user_name: str = None, user_id: str = None) -> dict: """ :param user_name: the user name for search (the user id) :param user_id: the user id for search (the user name) :return: {"name": <user name>, "id": <user id>} """ if user_name: if user_name.lower() == "me": user_name = self.__user_name looking_in = "username" looking_for = user_name elif user_id: looking_in = "id" looking_for = user_id else: return {} if not self.__cache.get("users"): _, _, self.__cache["users"] = self.get_user_list() for user in self.__cache["users"]: if user.get(looking_in) == looking_for: return {"id": user.get("id"), "name": user.get("username")} demisto.debug(f"{looking_for} is not a {looking_in}(user).") return {} def __org_and_id(self, org_name: str = None, org_id: str = None) -> dict: """ :param org_name: the org name for search (the org id) :param org_id: the org id for search (the org name) :return: {"name": <org name>, "id": <org id>} """ if not org_id: if not org_name: org_name = "None" looking_for = org_name looking_in = "name" else: looking_for = org_id looking_in = "id" if not self.__cache["org"]: _, _, self.__cache["org"] = self.get_organization_list(raw=True) for org in self.__cache["org"]: if org.get(looking_in) == looking_for: return org demisto.debug(f"{looking_for} is not a {looking_in}(org).") return {} def __status_and_id(self, status_name: str = None, status_id: str = None) -> dict: """ :param status_name: the status name for search (the status id) :param status_id: the status id for search (the status name) :return: {"name": <status name>, "id": <status id>} """ if not status_id: looking_for = status_name if status_name else "Open" looking_in = "name" else: looking_for = status_id looking_in = "id" if not self.__cache["status"]: def filter_statuses_data(status_dict: dict): try: status_dict.pop("showInCasePane") status_dict.pop("default") except KeyError: pass return status_dict self.__cache["status"] = list(map(filter_statuses_data, (self.get_case_statuses(raw=True))[2])) for status in self.__cache["status"]: if status.get(looking_in) == looking_for: return status demisto.debug(f"{looking_for} is not a {looking_in}(status).") return {} def get_user_list(self, raw: bool = False) -> tuple[str, dict, dict]: """ :param raw: ignore the human outputs if True :return: list of all Users """ path = "userGetUserList" headers = ["ID", "Name", "Email", "Groups", "IsMaster", "IsAdmin", "SMS"] raw_response = self.__request(path, data={"authPW": {"value": self.__password}}) result = raw_response context_entry: list = [dict] * len(result) human_readable = "" if not raw: for i in range(len(result)): context_entry[i] = { "ID": result[i].get("id"), "Name": result[i].get("username"), "Email": result[i].get("email"), "SMS": result[i].get("sms"), "IsMaster": result[i].get("master"), "IsAdmin": result[i].get("admin"), } if "groups" in result[i]: context_entry[i]["Groups"] = "".join(str(result[i]["groups"])) human_readable = tableToMarkdown(name="User list", t=context_entry, headers=headers) returned_context_entry = {f"{CONTEXT_INTEGRATION_NAME}User(val.ID && val.ID == obj.ID)": context_entry} return human_readable, returned_context_entry, raw_response def get_organization_list(self, raw: bool = False) -> tuple[str, dict, list[dict]]: """ :param raw: ignore the human outputs if True :return: list of all organizations """ path = "caseGetOrganizationList" raw_response = self.__request(path) entry: list = [None] * len(raw_response) context_entry: dict = {} human_readable: str = "" if not raw: for i in range(len(raw_response)): entry[i] = {"ID": raw_response[i].get("id"), "Name": raw_response[i].get("name")} context_entry = {f"{CONTEXT_INTEGRATION_NAME}Organization(val.ID && val.ID == obj.ID)": entry} human_readable = tableToMarkdown(name="Organizations", t=raw_response) return human_readable, context_entry, raw_response def get_case_list(self, start_time: str = None, raw: bool = False) -> tuple[str, dict, list]: """ :param raw: ignore the human outputs if True :return: list of all Users """ path = "caseGetCaseList" since = self.args.get("since", "1 year") context_entry = [] human_readable: str = "" if not raw and not start_time: _, start_time, _ = set_query_times(since=since, difference=self.difference) start_time = convert_time_format(str(start_time), difference=self.difference) raw_response: list = self.__request(path) result = raw_response for case in result: case = dict_times_set(case, self.difference) if not start_time or not start_time > case.get("openTime"): temp_case = { "ID": case.get("id"), "Summary": case.get("summary"), "OpenTime": case.get("openTime"), "Severity": case.get("severity"), } if "statusId" in case: status_id = case.get("statusId", {}) if isinstance(status_id, dict): status_id = status_id.get("value") temp_case["Status"] = self.__status_and_id(status_id=status_id).get("name") context_entry.append(temp_case) if not raw: human_readable = tableToMarkdown(name=f"cases since {since}", t=context_entry) returned_context_entry = {f"{CONTEXT_INTEGRATION_NAME}Case(val.ID && val.ID == obj.ID)": context_entry} return human_readable, returned_context_entry, raw_response def get_case_event_list(self) -> tuple[str, dict, list[dict]]: path = "caseGetCaseEventsDetail" ids = argToList(self.args.get("ids")) raw_response = self.__request(path, data={"eventIds": {"list": ids}}) result = raw_response case_event: list = [None] * len(result) for i in range(len(result)): result[i] = dict_times_set(result[i], self.difference) case_event[i] = { "ID": result[i].get("id"), "LastTime": result[i].get("lastTime"), "Message": result[i].get("message"), } context_entry = {f"{CONTEXT_INTEGRATION_NAME}CaseEvent(val.ID && val.ID == obj.ID)": case_event} human_readable = tableToMarkdown(name="case event list", t=result) return human_readable, context_entry, raw_response def get_case_detail(self, case_id: str = None, raw: bool = False) -> tuple[str, dict, dict]: path = "caseGetCaseDetail" raw_response = self.__request(path, data={"id": case_id if case_id else self.args.get("id")}) result = raw_response result = dict_times_set(result, difference=self.difference) status_id = result.get("statusId", {}) if not isinstance(status_id, int): status_id = status_id.get("value") context_entry = { "Assignee": self.__username_and_id(user_id=result.get("assignedTo")).get("name"), "ID": result.get("id"), "Summary": result.get("summary"), "Status": self.__status_and_id(status_id=status_id).get("name"), "OpenTime": result.get("openTime"), "Severity": result.get("severity"), "Organization": self.__org_and_id(org_id=result.get("orgId")).get("name"), "EventList": result.get("eventList"), "Notes": result.get("notes"), } human_readable = "" readable_outputs = context_entry del readable_outputs["Notes"] del readable_outputs["EventList"] if not raw: human_readable = tableToMarkdown(name="Case", t=readable_outputs) returned_context_entry = {f"{CONTEXT_INTEGRATION_NAME}Case(val.ID && val.ID == obj.ID)": context_entry} return human_readable, returned_context_entry, raw_response def get_case_statuses(self, raw: bool = False) -> tuple[str, dict, dict]: path = "caseGetCaseStatusList" headers = ["id", "name", "default", "showInCasePane"] raw_response = self.__request(path) human_readable = "" if not raw: human_readable = tableToMarkdown(name="case statuses", t=raw_response, headers=headers) return human_readable, {}, raw_response def add_case(self) -> tuple[str, dict, dict]: path = "caseAddCase" assignee = self.args.get("assignee") if not assignee: assignee = "ME" case_details = { "summary": self.args.get("summary"), "assignedTo": self.__username_and_id(user_name=assignee).get("id"), "severity": self.args.get("severity"), "orgId": self.__org_and_id(org_name=self.args.get("organization")).get("id"), "statusId": {"value": self.__status_and_id(status_name=self.args.get("status")).get("id")}, } result = self.__request(path, data={"caseDetail": case_details}) human_readable, context_entry, raw_response = self.get_case_detail(result.get("value")) return human_readable, context_entry, raw_response def edit_case(self) -> tuple[str, dict, dict]: path = "caseEditCase" _, _, result = self.get_case_detail(case_id=self.args.get("id")) if "organization" in self.args: result["orgId"] = self.__org_and_id(org_name=self.args.get("organization")).get("id") if "summary" in self.args: result["summary"] = self.args["summary"] if "assignee" in self.args: result["assignedTo"] = self.args["assignee"] if "severity" in self.args: result["severity"] = self.args["severity"] if "status" in self.args: result["statusId"] = {"value": self.__status_and_id(status_name=self.args["status"]).get("id")} if "notes" in self.args: result["notes"] = self.args["notes"] self.__request(path, data={"caseDetail": result}) return self.get_case_detail(case_id=self.args.get("id")) def add_case_status(self) -> tuple[str, dict, dict]: path = "caseAddCaseStatus" status_details = {"name": self.args.get("name"), "default": False} if "should_show_in_case_pane" in self.args: status_details["showInCasePane"] = self.args["should_show_in_case_pane"] raw_response = self.__request(path, data={"status": status_details}) self.__cache["status"] = {} status_id = status_details["name"] return f"Added case status : {status_id}", {}, raw_response def edit_case_status(self) -> tuple[str, dict, dict]: path = "caseEditCaseStatus" status_id = self.__status_and_id(status_name=self.args.get("original_name")).get("id") status_details = {"status": {"id": status_id, "name": self.args.get("new_name")}} if "show_in_case_pane" in self.args: status_details["status"]["showInCasePane"] = self.args.get("show_in_case_pane") raw_response = self.__request(path, data=status_details) self.__cache["status"] = {} return f"Edited case status with ID: {status_id}", {}, raw_response def delete_case_status(self) -> tuple[str, dict, dict]: path = "caseDeleteCaseStatus" status_id = self.__status_and_id(status_name=self.args.get("name")).get("id") self.__request(path, data={"statusId": {"value": status_id}}) self.__cache["status"] = {} return f"Deleted case status with ID: {status_id}", {}, {} def fetch_fields(self) -> tuple[str, dict, dict[str, list]]: path = "qryGetFilterFields" raw_response = self.__request(path) result = raw_response for field_type in result: field_type["types"] = ",".join(set(field_type["types"])) human_readable = tableToMarkdown(name="Fields", t=result) return human_readable, {}, raw_response def fetch_alarms( self, since: str = None, start_time: str = None, end_time: str = None, raw: bool = False ) -> tuple[str, dict, list]: path = "alarmGetTriggeredAlarms" human_readable = "" context_entry: list = [] since = since if since else self.args.get("timeRange") start_time = start_time if start_time else self.args.get("customStart") end_time = end_time if end_time else self.args.get("customEnd") since, start_time, end_time = set_query_times(since, start_time, end_time, self.difference) params = {"triggeredTimeRange": since} if since == "CUSTOM": params["customStart"] = start_time params["customEnd"] = end_time data = {} if assigned_user := self.args.get("assignedUser"): if assigned_user.lower() == "me": assigned_user = self.__user_name data = {"assignedUser": {"username": assigned_user, "id": self.__username_and_id(user_name=assigned_user).get("id")}} demisto.debug(f"sending request to fetch alarms with {start_time=}, {end_time=}") raw_response = self.__request(path, data=data, params=params) result = raw_response for i in range(len(result)): result[i] = dict_times_set(result[i], self.difference) if not raw: context_entry = [None] * len(result) for i in range(len(result)): context_entry[i] = { "ID": result[i].get("id"), "summary": result[i].get("summary"), "assignee": result[i].get("assignee"), "severity": result[i].get("severity"), "triggeredDate": result[i].get("triggeredDate"), "acknowledgedDate": result[i].get("acknowledgedDate"), "acknowledgedUsername": result[i].get("acknowledgedUsername"), "alarmName": result[i].get("alarmName"), "conditionType": result[i].get("conditionType"), } table_headers = [ "id", "acknowledgedDate", "acknowledgedUsername", "alarmName", "assignee", "conditionType", "severity", "summary", "triggeredDate", ] human_readable = tableToMarkdown(name="Alarms", t=result, headers=table_headers) returned_context_entry = {f"{CONTEXT_INTEGRATION_NAME}Alarm(val.ID && val.ID == obj.ID)": context_entry} return human_readable, returned_context_entry, raw_response def acknowledge_alarms(self) -> tuple[str, dict, dict]: try: self.__handle_alarms("Acknowledge") except DemistoException as error: # bug in ESM API performs the job but an error is return. if not expected_errors(error): raise error return "Alarms has been Acknowledged.", {}, {} def unacknowledge_alarms(self) -> tuple[str, dict, dict]: try: self.__handle_alarms("Unacknowledge") except DemistoException as error: # bug in ESM API performs the job but an error is return. if not expected_errors(error): raise error return "Alarms has been Unacknowledged.", {}, {} def delete_alarm(self) -> tuple[str, dict, dict]: self.__handle_alarms("Delete") return "Alarms has been Deleted.", {}, {} def __handle_alarms(self, command: str): path = f"alarm{command}TriggeredAlarm" alarm_ids = argToList(str(self.args.get("alarmIds"))) alarm_ids = [int(i) for i in alarm_ids] data = {"triggeredIds": {"alarmIdList": alarm_ids} if not self.version < "11.3" else alarm_ids} self.__request(path, data=data) def get_alarm_event_details(self) -> tuple[str, dict, dict]: path = "ipsGetAlertData" raw_response = self.__request(path, data={"id": self.args.get("eventId")}) result = raw_response result = dict_times_set(result, self.difference) context_entry = self.__alarm_event_context_and_times_set(result) human_readable = tableToMarkdown(name="Alarm events", t=context_entry) return human_readable, {f"{CONTEXT_INTEGRATION_NAME}AlarmEvent": context_entry}, raw_response def list_alarm_events(self) -> tuple[str, dict, dict]: path = "notifyGetTriggeredNotificationDetail" raw_response = self.__request(path, data={"id": self.args.get("alarmId")}) result = raw_response result = dict_times_set(result, self.difference) human_readable: str = "" context_entry: list = [] if "events" in result: context_entry = [dict] * len(result["events"]) for event in range(len(result["events"])): context_entry[event] = self.__alarm_event_context_and_times_set(result["events"][event]) human_readable = tableToMarkdown(name="Alarm events", t=context_entry) return ( human_readable, {f"{CONTEXT_INTEGRATION_NAME}AlarmEvent(val.ID && val.ID == obj.ID)": context_entry}, raw_response, ) def complete_search(self): time_out = int(self.args.get("timeOut", 30)) interval = min(10, time_out) search_id = self.__search() i = 0 while not self.__generic_polling(search_id): i += 1 time.sleep(interval) # pylint: disable=sleep-exists if i * interval >= time_out: raise DemistoException(f"Search: {search_id} time out.") return self.__search_fetch_result(search_id) def __search(self) -> int: path = "qryExecuteDetail" query_type = self.args.get("queryType") time_range = self.args.get("timeRange") custom_start = self.args.get("customStart") custom_end = self.args.get("customEnd") offset = self.args.get("offset") time_range, custom_start, custom_end = set_query_times(time_range, custom_start, custom_end, self.difference) time_config = {"timeRange": time_range} if time_range == "CUSTOM": time_config["customStart"] = custom_start time_config["customEnd"] = custom_end params = {"reverse": False, "type": query_type if query_type else "EVENT"} config = {"filters": json.loads(self.args.get("filters")), "limit": self.args.get("limit", 0)} fields = self.args.get("fields") if fields: config["fields"] = [{"name": field} for field in argToList(fields)] if offset: config["offset"] = offset config.update(time_config) result = self.__request(path, data={"config": config}, params=params) return result.get("resultID") def __generic_polling(self, search_id: Union[str, int]) -> bool: if not search_id: search_id = self.args.get("SearchID") path = "qryGetStatus" status = self.__request(path, data={"resultID": str(search_id)}) return status.get("complete") def __search_fetch_result(self, search_id: int) -> tuple[str, dict, dict]: path = "qryGetResults" params = {"startPos": 0, "reverse": False, "numRows": self.args.get("ratePerFetch", 50)} result_ready = False raw_response: dict[str, list] = {"columns": [], "rows": []} while not result_ready: try: temp = self.__request(path, data={"resultID": search_id}, params=params) if not raw_response["columns"]: raw_response["columns"] = temp.get("columns") if len(temp.get("rows", {})) < params["numRows"]: result_ready = True raw_response["rows"].extend(temp.get("rows")) params["startPos"] += params["numRows"] except DemistoException as error: if not expected_errors(error): raise else: result_ready = True result = raw_response result = table_times_set(result, self.difference) entry: list = [{}] * len(result["rows"]) headers = [str(field.get("name")).replace(".", "") for field in result["columns"]] for i in range(len(result["rows"])): entry[i] = {headers[j]: result["rows"][i]["values"][j] for j in range(len(headers))} condition = ( "(val.AlertIPSIDAlertID && val.AlertIPSIDAlertID == obj.AlertIPSIDAlertID)" if "AlertIPSIDAlertID" in headers else "" ) context_entry = {f"{CONTEXT_INTEGRATION_NAME}results{condition}": entry} return search_readable_outputs(result), context_entry, raw_response def __alarm_event_context_and_times_set(self, result: dict) -> dict: context_entry = { "ID": result.get("eventId", result.get("alertId")), "SubType": result.get("subtype", result.get("eventSubType")), "Severity": result.get("severity"), "Message": result.get("ruleName", result.get("ruleMessage")), "LastTime": result.get("lastTime"), "SrcIP": result.get("srcIp", result.get("sourceIp")), "DstIP": result.get("destIp", result.get("destIp")), "DstMac": result.get("destMac"), "SrcMac": result.get("srcMac"), "DstPort": result.get("destPort"), "SrcPort": result.get("srcPort"), "FirstTime": result.get("firstTime"), "NormalizedDescription": result.get("normDesc"), } if "cases" in result: cases: list = [None] * len(result["cases"]) for i in range(len(result["cases"])): case_status = self.__status_and_id(status_id=result["cases"][i].get("statusId", {}).get("value")) cases[i] = { "ID": result["cases"][i].get("id"), "OpenTime": result["cases"][i].get("openTime"), "Severity": result["cases"][i].get("severity"), "Status": case_status.get("name"), "Summary": result["cases"][i].get("summary"), } context_entry["Case"] = cases return context_entry def fetch_incidents(self, params: dict): last_run = demisto.getLastRun() current_run = {} incidents = [] if params.get("fetchType", "alarms") in ("alarms", "both"): start_time = last_run.get("alarms", {}).get("time", parse_date_range(params.get("fetchTime"), self.demisto_format)[0]) start_id = int(last_run.get("alarms", {}).get("id", params.get("startingFetchID"))) temp_incidents, current_run["alarms"] = self.__alarms_to_incidents( start_time, start_id, int(params.get("fetchLimitAlarms", 5)) ) incidents.extend(temp_incidents) if params.get("fetchType") in ("cases", "both"): start_id = int(last_run.get("cases", {}).get("id", params.get("startingFetchID"))) temp_incidents, current_run["cases"] = self.__cases_to_incidents( start_id=start_id, limit=int(params.get("fetchLimitCases", 5)) ) incidents.extend(temp_incidents) demisto.setLastRun(current_run) demisto.incidents(incidents) def __alarms_to_incidents(self, start_time: str, start_id: int = 0, limit: int = 1) -> tuple[list, dict]: current_time = datetime.utcnow().strftime(self.demisto_format) current_run = {} _, _, all_alarms = self.fetch_alarms(start_time=start_time, end_time=current_time, raw=True) all_alarms = filtering_incidents(all_alarms, start_id=start_id, limit=limit) if all_alarms: current_run["time"] = all_alarms[0].get("triggeredDate", start_time) current_run["id"] = all_alarms[0]["id"] current_run_time = current_run["time"] demisto.debug(f"{len(all_alarms)=}, setting current time to {current_run_time=}") else: current_run["time"] = start_time current_run["id"] = start_id demisto.debug(f"No alarms were found, setting current time to {start_time=}") all_alarms = create_incident(all_alarms, alarms=True) return all_alarms, current_run def __cases_to_incidents(self, start_id: int = 0, limit: int = 1) -> tuple[list, dict]: _, _, all_cases = self.get_case_list(raw=True) all_cases = filtering_incidents(all_cases, start_id=start_id, limit=limit) current_run = {"id": all_cases[0].get("id", start_id) if all_cases else start_id} all_cases = create_incident(all_cases, alarms=False) return all_cases, current_run def __get_watchlists(self, args: dict): command = "sysGetWatchlists" params = { "hidden": args.get("hidden", True), "dynamic": args.get("dynamic", True), "writeOnly": args.get("write_only", False), "indexedOnly": args.get("indexed_only", False), } return self.__request(command, params=params) def __get_watchlist_id(self, watchlist_name: str): try: return list(filter(lambda x: x.get("name") == watchlist_name, self.__get_watchlists({})))[0].get("id") except IndexError: raise DemistoException(f"Can not find the watchlist {watchlist_name}") def get_watchlists_names_and_ids(self): raw_watch_lists = self.__get_watchlists(self.args) watch_lists = list(map(format_watchlist_params, raw_watch_lists)) human_readable = tableToMarkdown("McAfee ESM Watchlist", t=watch_lists) return human_readable, {f"{CONTEXT_INTEGRATION_NAME}Watchlist": watch_lists}, raw_watch_lists def add_watchlist(self): command = "sysAddWatchlist" watchlist_name = self.args.get("name") watchlist_type = self.args.get("type") data = { "watchlist": { "name": watchlist_name, "type": {"name": watchlist_type, "id": 0}, "customType": {"name": "", "id": 0}, "dynamic": "False", "enabled": "True", } } watchlist_id = self.__request(command, data=data) context_entry = { "name": watchlist_name, "id": watchlist_id.get("value"), "type": watchlist_type, } human_readable = f"Watchlist {watchlist_name} created." return human_readable, {f"{CONTEXT_INTEGRATION_NAME}Watchlist": context_entry}, watchlist_id def delete_watchlist(self): command = "sysRemoveWatchlist" ids_to_delete = argToList(self.args.get("ids", "")) ids_to_delete.extend(list(map(self.__get_watchlist_id, argToList(self.args.get("names"))))) if self.version.startswith("11."): data = {"ids": {"watchlistIdList": ids_to_delete}} self.__request(command, data) else: for single_id in ids_to_delete: data = {"id": single_id} self.__request(command, data) return "Watchlists removed", {}, {} def watchlist_add_entry(self): command = "sysAddWatchlistValues" watchlist_id = self.args.get("watchlist_id") data = { "watchlist": watchlist_id if watchlist_id else self.__get_watchlist_id(self.args.get("watchlist_name", "")), "values": argToList(self.args.get("values", "")), } raw_response = self.__request(command, data=data) human_readable = "Watchlist successfully updated." return human_readable, {}, raw_response def watchlist_delete_entry(self): command = "sysRemoveWatchlistValues" watchlist_id = self.args.get("watchlist_id") data = { "watchlist": watchlist_id if watchlist_id else self.__get_watchlist_id(self.args.get("watchlist_name", "")), "values": argToList(self.args.get("values", "")), } self.__request(command, data=data) human_readable = "Watchlist successfully updated." return human_readable, {}, {} def __get_watchlist_file_id(self, watchlist_id: int): command = "sysGetWatchlistDetails" result = self.__request(command, data={"id": watchlist_id}) # v10.x uses 'valueCount' while v11.x uses 'recordCount'. count_results = result.get("recordCount") or result.get("valueCount") if not count_results: raise EmptyFile value_file = result.get("valueFile", {}) file_token = value_file.get("fileToken", value_file.get("id")) watchlist_name = result.get("name") return file_token, watchlist_name def watchlist_data_list(self): watchlist_id = self.args.get("watchlist_id") watchlist_id = watchlist_id if watchlist_id else self.__get_watchlist_id(self.args.get("watchlist_name", "")) try: file_token, watchlist_name = self.__get_watchlist_file_id(watchlist_id) except EmptyFile: return "the watchlist is empty.", {}, {} else: max_values = int(self.args.get("limit", 50)) offset = int(self.args.get("offset", 0)) file_data = [] for i, line in enumerate(self.watchlist_values(file_token)): if i < offset: continue file_data.append(line) if len(file_data) >= max_values: break human_readable = tableToMarkdown(f"results from {watchlist_name} watchlist", t={"data": file_data}) context_entry = {f"{CONTEXT_INTEGRATION_NAME}Watchlist": {"data": file_data, "name": watchlist_name}} return human_readable, context_entry, file_data def watchlist_values(self, file_token: str, buff_size=400): """ :param file_token: the token file (McAfee API call needed for the file creation) :param buff_size: the size of the bytes in every API call. :return: generate values (string) """ command = "sysGetWatchlistValues" data = {"file": {"id": file_token}} end = "" for i in itertools.count(start=0): params = {"pos": i * buff_size, "count": buff_size} result = self.__request(command, data=data, params=params) file_data = "{}{}".format(end, result.get("data", "")) file_data = file_data.split("\n") more_data_exist = buff_size == result.get("bytesRead") or not file_data if more_data_exist: end = file_data[-1] file_data = file_data[:-1] yield from (line for line in file_data if line) if not more_data_exist: break def filtering_incidents(incidents_list: list, start_id: int, limit: int = 1): """ :param incidents_list: list of al incidents :param start_id: id to start from :param limit: limit :return: the filtered incidents """ filtered_incident = [] ignored_incident_ids = [] for incident in incidents_list: if int(incident.get("id", 0)) > start_id: filtered_incident.append(incident) else: ignored_incident_ids.append(incident.get("id")) demisto.debug(f"filtered {len(ignored_incident_ids)} incidents by {start_id=}.\n{ignored_incident_ids=}") filtered_incident.sort(key=lambda incident: int(incident.get("id", 0)), reverse=True) if limit != 0: incidents_size = min(limit, len(filtered_incident)) filtered_incident = filtered_incident[-incidents_size:] return filtered_incident def expected_errors(error: DemistoException) -> bool: """ :param error: the error :return: if the error is not real error """ expected_error: list[str] = [ "qryGetResults failed with error[Error deserializing EsmQueryResults, see logs for more information " # noqa: W504 + "(Error deserializing EsmQueryResults, see logs for more information " # noqa: W504 + "(Internal communication error, see logs for more details))].", "alarmUnacknowledgeTriggeredAlarm failed with error[ERROR_BadRequest (60)].", "alarmAcknowledgeTriggeredAlarm failed with error[ERROR_BadRequest (60)].", ] return str(error) in expected_error def time_format(current_time: str, difference: int = 0) -> str: """ :param current_time: the current time in the current format :param difference: the time zone offset :return: the time in the new format and in UTC time """ to_return: str = "" try: to_return = convert_time_format(current_time, difference=difference, mcafee_format="%Y/%m/%d %H:%M:%S") except ValueError as error: if str(error) != f"time data '{current_time}' does not match format '%Y/%m/%d %H:%M:%S'": raise error try: to_return = convert_time_format(current_time, difference=difference, mcafee_format="%m/%d/%Y %H:%M:%S") except ValueError as error_2: if str(error_2) != f"time data '{current_time}' does not match format '%m/%d/%Y %H:%M:%S'": raise error_2 try: to_return = convert_time_format(current_time, difference=difference, mcafee_format="%d-%m-%Y %H:%M:%S") except ValueError as error_3: if str(error_3) != f"time data '{current_time}' does not match format '%d-%m-%Y %H:%M:%S'": raise error_3 else: raise ValueError(f"time data '{current_time}' does not match the time format.") return to_return def convert_time_format( current_time: str, difference: int = 0, to_demisto: bool = True, mcafee_format: str = "%Y/%m/%d %H:%M:%S" ) -> str: """ :param current_time: the current_time :param difference: the difference (e.g. time zone) :param to_demisto: true if we want change the time zone from McAfee ESM time to demisto (e.g. UTC) :param mcafee_format: the standard format in McAfee Machine :return: the new format """ if not current_time.endswith("(GMT)"): if not to_demisto and not current_time.endswith("Z"): current_time += "Z" datetime_obj = datetime.strptime(current_time, mcafee_format if to_demisto else McAfeeESMClient.demisto_format) datetime_obj -= timedelta(hours=difference if to_demisto else -1 * difference) else: datetime_obj = datetime.strptime(current_time, "%m/%d/%Y %H:%M:%S(GMT)") return datetime_obj.strftime(McAfeeESMClient.demisto_format) def set_query_times( since: str = None, start_time: str = None, end_time: str = None, difference: int = 0 ) -> tuple[str | None, str | None, str | None]: """ checks all time args :param since: since from args :param start_time: start_time from args :param end_time: end_time from args :param difference: the difference (e.g. time zone) :return: the args in the machine time and after validation """ if not since: since = "CUSTOM" elif start_time or end_time: raise ValueError("Invalid set times.") if since != "CUSTOM" and " " in since: start_time, _ = parse_date_range(since, "%Y/%m/%d %H:%M:%S") else: if start_time: start_time = convert_time_format(start_time, difference=difference, to_demisto=False) if end_time: end_time = convert_time_format(end_time, difference=difference, to_demisto=False) if start_time and end_time and start_time > end_time: raise ValueError("Invalid set times.") return since, start_time, end_time def list_times_set(list_to_set: list, indexes: list, difference: int = 0) -> list: """ :param list_to_set: the raw list :param indexes: a list of the indexes for time fields :param difference: the difference (e.g. time zone) :return: the data list with utc times """ for i in indexes: if list_to_set[i]: list_to_set[i] = time_format(list_to_set[i], difference=difference) return list_to_set def dict_times_set(dict_to_set: dict, difference: int = 0) -> dict: """ :param dict_to_set: the raw dict :param difference: the difference (e.g. time zone) :return: the data dict with utc times """ for field in dict_to_set: if dict_to_set[field]: if "time" in field.lower() or "date" in field.lower(): dict_to_set[field] = time_format(dict_to_set[field], difference=difference) elif isinstance(dict_to_set[field], dict): dict_to_set[field] = dict_times_set(dict_to_set[field], difference) elif isinstance(dict_to_set[field], list): for i in range(len(dict_to_set[field])): if isinstance(dict_to_set[field][i], dict): dict_to_set[field][i] = dict_times_set(dict_to_set[field][i], difference) return dict_to_set def time_fields(field_list: list[dict]) -> list: """ :param field_list: the list of fields for a given query :return: all fields (names only) that have a time value """ indexes_list = [] for i in range(len(field_list)): if "time" in field_list[i]["name"].lower() or "date" in field_list[i]["name"].lower(): indexes_list.append(i) return indexes_list def table_times_set(table_to_set: dict, difference: int = 0) -> dict: """ :param table_to_set: the raw event/ alarm :param difference: the difference (e.g. time zone) :return: the event/ alarm with utc time """ indexes_list = time_fields(table_to_set["columns"]) for dict_ in table_to_set["rows"]: dict_["values"] = list_times_set(dict_.get("values"), indexes_list, difference) return table_to_set def search_readable_outputs(table: dict) -> str: """ :param table: the raw data for a search :return: md format table """ if "columns" in table and "rows" in table: line_1 = line_2 = "|" for header in table.get("columns", []): line_1 += str(header.get("name")) + "|" line_2 += "--|" rows = table["rows"] data: list = [str] * len(rows) for i in range(len(rows)): middle = "~".join(rows[i].get("values", [])) middle = middle.replace("|", "\\|") middle = middle.replace("~", "|") data[i] = f"| {middle} |" start = f"Search results\n{line_1}\n{line_2}\n" return start + "\n".join(data) else: return "" def create_incident(raw_incidents: list[dict], alarms: bool) -> list[dict[str, dict]]: incidents = [] for incident in raw_incidents: alarm_id = str(incident.get("id")) summary = str(incident.get("summary")) incident_type = "alarm" if alarms else "case" incidents.append( { "name": f"McAfee ESM {incident_type}. id: {alarm_id}. {summary}", "severity": mcafee_severity_to_demisto(incident.get("severity", 0)), "occurred": incident.get("triggeredDate", incident.get("openTime", "")), "rawJSON": json.dumps(incident), } ) return incidents def mcafee_severity_to_demisto(severity: int) -> int: if severity > 65: return 3 elif severity > 32: return 2 elif severity > 0: return 1 else: return 0 def format_watchlist_params(raw_watchlist_params: dict): return { "id": raw_watchlist_params.get("id"), "name": raw_watchlist_params.get("name"), "type": dict_safe_get(raw_watchlist_params, ["type", "name"]), } def main(): client = McAfeeESMClient(demisto.params()) command = demisto.command() commands: dict[str, Callable] = { "test-module": client.test_module, "esm-fetch-fields": client.fetch_fields, "esm-get-organization-list": client.get_organization_list, "esm-fetch-alarms": client.fetch_alarms, "esm-add-case": client.add_case, "esm-get-case-detail": client.get_case_detail, "esm-edit-case": client.edit_case, "esm-get-case-statuses": client.get_case_statuses, "esm-edit-case-status": client.edit_case_status, "esm-get-case-event-list": client.get_case_event_list, "esm-add-case-status": client.add_case_status, "esm-delete-case-status": client.delete_case_status, "esm-get-case-list": client.get_case_list, "esm-get-user-list": client.get_user_list, "esm-acknowledge-alarms": client.acknowledge_alarms, "esm-unacknowledge-alarms": client.unacknowledge_alarms, "esm-delete-alarms": client.delete_alarm, "esm-get-alarm-event-details": client.get_alarm_event_details, "esm-list-alarm-events": client.list_alarm_events, "esm-search": client.complete_search, "esm-get-watchlists": client.get_watchlists_names_and_ids, "esm-create-watchlist": client.add_watchlist, "esm-delete-watchlist": client.delete_watchlist, "esm-watchlist-add-entry": client.watchlist_add_entry, "esm-watchlist-delete-entry": client.watchlist_delete_entry, "esm-watchlist-list-entries": client.watchlist_data_list, } try: if command == "fetch-incidents": client.fetch_incidents(demisto.params()) elif command in commands: human_readable, context_entry, raw_response = commands[command]() return_results(CommandResults(readable_output=human_readable, outputs=context_entry, raw_response=raw_response)) else: raise NotImplementedError(f"{command} is not a demisto command.") except Exception as error: return_error(str(error), error) if __name__ in ("__main__", "__builtin__", "builtins"): main()