SumoLogicSEC
Freeing the analyst with autonomous decisions.
Analytics & SIEM · Sumo Logic Cloud SIEM
Details
| ID | SumoLogicSEC |
|---|---|
| Provider | Francisco Partners |
| Category | Analytics & SIEM |
| From Version | 5.0.0 |
| Docker Image | demisto/python3:3.12.13.10116658 |
| Supported Modules | Agentix XSIAM |
README
Freeing the analyst with autonomous decisions.
This integration was integrated and tested with version 6.1.0 of SumoLogicSEC.
Prerequisites
Only use this integration if your Cloud SIEM portal url ends with .sumologic.com - this can be verified via the url in your browser when logged into Cloud SIEM.
You’ll need an access key in order to complete the instance setup. Instructions on how to generate access keys can be found here.
Configure SumoLogicSEC in Cortex
| Parameter | Description | Required |
|---|---|---|
| Sumo Logic API Endpoint | https://api.<deployment>.sumologic.com/api | True |
| Sumo Logic Instance Endpoint | https://<yoursubdomain>.<deployment>.sumologic.com | False |
| Fetch incidents | False | |
| Incident type | False | |
| Access ID | True | |
| Access Key | True | |
| Incidents Fetch Interval | False | |
| Fetch Limit | Fetch limit must not be greater than 200 | False |
| Override default fetch query | Default fetch query is status:in(“new”, “inprogress”) | False |
| First fetch time | False | |
| Pull associated Sumo Logic Signals as Incidents | Pull Sumo Logic signals associated with detected Sumo Logic Insights as XSOAR incidents | False |
| Incident Mirroring Direction | Choose the direction to mirror the incident: Incoming (from Sumo Logic SIEM to Cortex XSOAR), Outgoing (from Cortex XSOAR to Sumo Logic SIEM), or Incoming and Outgoing (from/to Cortex XSOAR and Sumo Logic SIEM). Cortex XSOAR only parameter. | False |
| Close Mirrored XSOAR Incident | When selected, closing the Sumo Logic SIEM Insight is mirrored in Cortex XSOAR. Cortex XSOAR only parameter. | False |
| Close Mirrored Sumo Logic Insight | When selected, closing the Cortex XSOAR incident is mirrored in Sumo Logic SIEM. | False |
| Override Record Summary Fields | Record Summary Fields included when fetching Insights | False |
API documentation and query examples
For commands with query parameter input the available fields and operators are documented in API docs. These docs are useful when executing queries using the following commands:
sumologic-sec-insight-searchsumologic-sec-signal-searchsumologic-sec-entity-search
To access the API documentation, select the link for your deployment from here. Add sec to the end of the url to access Cloud SIEM API docs - e.g. https://api.us2.sumologic.com/docs/sec/.
Example: Insight search query ‘q’ parameter:
The search query string in our custom DSL that is used to filter the results.
Operators:
exampleField:"bar": The value of the field is equal to “bar”.exampleField:in("bar", "baz", "qux"): The value of the field > is equal to either “bar”, “baz”, or “qux”.exampleTextField:contains("foo bar"): The value of the field > contains the phrase “foo bar”.exampleNumField:>5: The value of the field is greater than 5. There are similar<,<=, and>=operators.exampleNumField:5..10: The value of the field is between 5 and 10 (inclusive).exampleDateField:>2019-02-01T05:00:00+00:00: The value of the date field is after 5 a.m. UTC time on February 2, 2019.exampleDateField:2019-02-01T05:00:00+00:00..2019-02-01T08:00:00+00:00: The value of the date field is between 5 a.m. and 8 a.m. UTC time on February 2, 2019.Fields:
idreadableIdstatusnameinsightIddescriptioncreatedtimestampclosedassigneeentity.ipentity.hostnameentity.usernameentity.typeenrichmenttagseverityresolutionruleIdrecords
Migrating from JASK content pack
The table below shows differences between this integration and the legacy JASK integration:
| JASK (legacy) | Sumo Logic Cloud SIEM | Notes |
|---|---|---|
| jask-get-insight-details | sumologic-sec-insight-get-details | |
| jask-get-insight-comments | sumologic-sec-insight-get-comments | |
| jask-get-signal-details | sumologic-sec-signal-get-details | |
| jask-get-entity-details | sumologic-sec-entity-get-details | |
| jask-get-related-entities | Deprecated | |
| jask-get-whitelisted-entities | Deprecated - use command sumologic-sec-entity-search with filter whitelisted:"true" |
|
| jask-search-insights | sumologic-sec-insight-search | |
| jask-search-entities | sumologic-sec-entity-search | |
| jask-search-signals | sumologic-sec-signal-search |
New commands introduced in Sumo Logic Cloud SIEM pack
sumologic-sec-insight-set-statussumologic-sec-match-list-getsumologic-sec-match-list-updatesumologic-sec-threat-intel-search-indicatorssumologic-sec-threat-intel-get-sourcessumologic-sec-threat-intel-update-source
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.
sumologic-sec-insight-get-details
Get Insight details for a specific Insight ID.
Base Command
sumologic-sec-insight-get-details
Input
| Argument Name | Description | Required |
|---|---|---|
| insight_id | The insight to retrieve details for. | Required |
| record_summary_fields | Record Summary Fields to include in the output (override default fields). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SumoLogicSec.Insight.Assignee | string | User or team assigned to the Insight |
| SumoLogicSec.Insight.Closed | Date | Closed date |
| SumoLogicSec.Insight.ClosedBy | String | Closed by user |
| SumoLogicSec.Insight.Created | Date | Created date |
| SumoLogicSec.Insight.Description | String | Description of the Insight |
| SumoLogicSec.Insight.Entity | String | Entity name associated with the Insight |
| SumoLogicSec.Insight.Id | String | The ID of the Insight |
| SumoLogicSec.Insight.LastUpdated | Date | The time the Insight was last updated |
| SumoLogicSec.Insight.LastUpdatedBy | string | The last user to update the Insight |
| SumoLogicSec.Insight.Name | String | The name of the Insight |
| SumoLogicSec.Insight.ReadableId | String | The ID of the Insight in readable form |
| SumoLogicSec.InsightList.RecordSummaryFields | Array | Record Summary Fields associated with the Insight |
| SumoLogicSec.Insight.Resolution | String | Resolution for closed Insight |
| SumoLogicSec.Insight.Severity | String | The severity of the Insight |
| SumoLogicSec.Insight.Signals.contentType | String | Type of content that triggered the Signal |
| SumoLogicSec.Insight.Signals.description | String | Description of the Signal |
| SumoLogicSec.Insight.Signals.id | String | The ID of the Signal |
| SumoLogicSec.Insight.Signals.name | String | The name of the Signal |
| SumoLogicSec.Insight.Signals.recordCount | Number | Number of records associated with the Signal |
| SumoLogicSec.Insight.Signals.ruleId | String | Rule ID associated with the Signal |
| SumoLogicSec.Insight.Signals.severity | Number | The severity of the Signal |
| SumoLogicSec.Insight.Signals.stage | String | The stage of the Signal |
| SumoLogicSec.Insight.Signals.timestamp | Date | Signal timestamp |
| SumoLogicSec.Insight.Source | String | The source of the Insight |
| SumoLogicSec.Insight.Status | String | The status of the Insight |
| SumoLogicSec.Insight.TimeToDetection | Number | Insight time to detection |
| SumoLogicSec.Insight.TimeToRemediation | Number | Insight time to remediation |
| SumoLogicSec.Insight.TimeToResponse | Number | Insight time to response |
| SumoLogicSec.Insight.Timestamp | Date | Insight timestamp |
Command Example
!sumologic-sec-insight-get-details insight-id=INSIGHT-116
Human Readable Output
Insight Details:
| Id | Readable Id | Name | Action | Status | Assignee | Description | Last Updated | Last Updated By | Severity | Closed | Closed By | Timestamp | Entity | Resolution |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| c6c97d84-983d-303e-a03b-86f53d657fc8 | INSIGHT-116 | Lateral Movement with Discovery and Credential Access | Closed | Initial Access, Lateral Movement, Discovery, Initial Access, Credential Access | 2021-05-10T23:48:10.016204 | HIGH | 2021-05-10T23:48:09.961023 | obfuscated@email.com | 2021-02-18T22:04:08.330000 | 1.2.3.4 | No Action |
sumologic-sec-insight-get-comments
Get comments for a specific Insight ID. (Users can post and update comments on the Sumo Logic Cloud SIEM portal for any Insight ID.)
Base Command
sumologic-sec-insight-add-comment
Input
| Argument Name | Description | Required |
|---|---|---|
| insight_id | The insight ID for which to add a comment. | Required |
| comment | The comment to be added. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SumoLogicSec.InsightComments.Id | String | ID of comment |
| SumoLogicSec.InsightComments.Body | String | Comment contents |
| SumoLogicSec.InsightComments.Author | String | User that created the comment |
| SumoLogicSec.InsightComments.Timestamp | Date | Comment created timestamp |
| SumoLogicSec.InsightComments.InsightId | String | The ID of the Insight |
Command Example
!sumologic-sec-insight-add-comment insight-id=INSIGHT-116 comment="This is an example comment"
Human Readable Output
Insight Comment:
| Id | Insight Id | Author | Body | Last Updated | Timestamp |
|---|---|---|---|---|---|
| 2 | INSIGHT-116 | obfuscated@email.com | This is an example comment | 2021-04-23T00:38:43.977543 |
sumologic-sec-insight-get-comments
Get comments for a specific Insight ID. (Users can post and update comments on the Sumo Logic Cloud SIEM portal for any Insight ID.)
Base Command
sumologic-sec-insight-get-comments
Input
| Argument Name | Description | Required |
|---|---|---|
| insight_id | The insight ID for which to retrieve comments. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SumoLogicSec.InsightComments.Id | String | ID of comment |
| SumoLogicSec.InsightComments.Body | String | Comment contents |
| SumoLogicSec.InsightComments.Author | String | User that created the comment |
| SumoLogicSec.InsightComments.Timestamp | Date | Comment created timestamp |
| SumoLogicSec.InsightComments.InsightId | String | The ID of the Insight |
Command Example
!sumologic-sec-insight-get-comments insight-id=INSIGHT-116
Human Readable Output
Insight Comments:
| Id | Insight Id | Author | Body | Last Updated | Timestamp |
|---|---|---|---|---|---|
| 2 | INSIGHT-116 | obfuscated@email.com | This is an example comment | 2021-04-23T00:38:43.977543 |
sumologic-sec-signal-get-details
Get Signal details for a specific Signal ID. Signal details command references signals in Sumo Logic Cloud SIEM which are created when records exhibit suspicious properties and mate with patterns or other detection logic.
Base Command
sumologic-sec-signal-get-details
Input
| Argument Name | Description | Required |
|---|---|---|
| signal_id | The signal to retrieve details for. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SumoLogicSec.Signal.ContentType | String | Type of content that triggered the Signal |
| SumoLogicSec.Signal.Description | String | Description of the Signal |
| SumoLogicSec.Signal.Entity | String | Entity name associated with the Signal |
| SumoLogicSec.Signal.Id | String | The ID of the Signal |
| SumoLogicSec.Signal.Name | String | The name of the Signal |
| SumoLogicSec.Signal.RecordCount | Number | Number of records associated with the Signal |
| SumoLogicSec.Signal.RuleId | String | Rule ID associated with the Signal |
| SumoLogicSec.Signal.Severity | Number | The severity of the Signal |
| SumoLogicSec.Signal.Stage | String | The stage of the Signal |
| SumoLogicSec.Signal.Suppressed | Boolean | Whether or not the Signal was suppressed |
| SumoLogicSec.Signal.Timestamp | Date | Signal timestamp |
Command Example
!sumologic-sec-signal-get-details signal-id=e0e7096b-2f91-5b72-b1a2-db48ce882dfc
Human Readable Output
Signal Details:
| Id | Name | Rule Id | Description | Severity | Content Type | Timestamp | Entity |
|---|---|---|---|---|---|---|---|
| e0e7096b-2f91-5b72-b1a2-db48ce882dfc | Potential malicious JVM download | LEGACY-S00062 | A document was downloaded and opened followed by a file download using a Java user-agent. | 4 | RULE | 2021-02-18T22:04:08.230000 | 1.2.3.4 |
sumologic-sec-entity-get-details
Get entity details for a specific entity ID
Base Command
sumologic-sec-entity-get-details
Input
| Argument Name | Description | Required |
|---|---|---|
| entity-id | The entity to retrieve details for. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SumoLogicSec.Entity.ActivityScore | Number | Entity Activity Score |
| SumoLogicSec.Entity.FirstSeen | Date | When the Entity was first seen |
| SumoLogicSec.Entity.Hostname | String | Entity hostname |
| SumoLogicSec.Entity.Id | String | Entity ID |
| SumoLogicSec.Entity.IsWhitelisted | Boolean | Whether or not the Entity is on allow list |
| SumoLogicSec.Entity.LastSeen | Date | When the Entity was last seen |
| SumoLogicSec.Entity.Name | String | The Entity name |
| SumoLogicSec.Entity.OperatingSystem | String | Entity Operating System (observed or from inventory) |
| SumoLogicSec.Entity.InventoryData | Boolean | Whether or not this Entity was ingested from inventory e.g. Active Directory |
Command Example
!sumologic-sec-entity-get-details entity-id=_hostname-win10--admin.obfuscated
Human Readable Output
Entity Details
| Id | Name | First Seen | Last Seen | Activity Score | Is Whitelisted | Operating System | Inventory Data |
|---|---|---|---|---|---|---|---|
| _hostname-win10–admin.obfuscated | win10-admin.obfuscated | 2021-04-21T14:43:38.526000 | 9 | false | Windows 10 Enterprise | true |
sumologic-sec-insight-search
Search insights using available filters
Base Command
sumologic-sec-insight-search
Input
| Argument Name | Description | Required |
|---|---|---|
| query | Use a query string to search, see API documentation for more details. | Optional |
| created | When the insight was created. Defaults to ‘All time’ if no time arguments are specified. Possible values are: All time, Last week, Last 48 hours, Last 24 hours. | Optional |
| status | Comma separated list of values from the options: new,inprogress,closed. | Optional |
| asignee | User assigned to Insights. | Optional |
| offset | The number of items to skip before starting to collect the result set. Default is 0. | Optional |
| limit | The maximum number of items to return. Default is 10. | Optional |
| record_summary_fields | Record Summary Fields to include in the output (override default fields). | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SumoLogicSec.InsightList.Assignee | String | User or team assigned to the Insight |
| SumoLogicSec.InsightList.Closed | Date | Closed date |
| SumoLogicSec.InsightList.ClosedBy | String | Closed by user |
| SumoLogicSec.InsightList.Created | Date | Created date |
| SumoLogicSec.InsightList.Description | String | Description of the Insight |
| SumoLogicSec.InsightList.Entity | String | Entity name associated with the Insight |
| SumoLogicSec.InsightList.Id | String | The ID of the Insight |
| SumoLogicSec.InsightList.LastUpdated | Date | The time the Insight was last updated |
| SumoLogicSec.InsightList.LastUpdatedBy | String | The last user to update the Insight |
| SumoLogicSec.InsightList.Name | String | The name of the Insight |
| SumoLogicSec.InsightList.ReadableId | String | The ID of the Insight in readable form |
| SumoLogicSec.InsightList.RecordSummaryFields | Array | Record Summary Fields associated with the Insight |
| SumoLogicSec.InsightList.Resolution | String | Resolution for closed Insight |
| SumoLogicSec.InsightList.Severity | String | The severity of the Insight |
| SumoLogicSec.InsightList.Signals.contentType | String | Type of content that triggered the Signal |
| SumoLogicSec.InsightList.Signals.description | String | Description of the Signal |
| SumoLogicSec.InsightList.Signals.id | String | The ID of the Signal |
| SumoLogicSec.InsightList.Signals.name | String | The name of the Signal |
| SumoLogicSec.InsightList.Signals.recordCount | Number | Number of records associated with the Signal |
| SumoLogicSec.InsightList.Signals.ruleId | String | Rule ID associated with the Signal |
| SumoLogicSec.InsightList.Signals.severity | Number | The severity of the Signal |
| SumoLogicSec.InsightList.Signals.stage | String | The stage of the Signal |
| SumoLogicSec.InsightList.Signals.timestamp | Date | Signal timestamp |
| SumoLogicSec.InsightList.Source | String | The source of the Insight |
| SumoLogicSec.InsightList.Status | String | The status of the Insight |
| SumoLogicSec.InsightList.TimeToDetection | Number | Insight time to detection |
| SumoLogicSec.InsightList.TimeToRemediation | Number | Insight time to remediation |
| SumoLogicSec.InsightList.TimeToResponse | Number | Insight time to response |
| SumoLogicSec.InsightList.Timestamp | Date | Insight timestamp |
Command Example
!sumologic-sec-insight-search query="timestamp:>\"2021-02-01T05:00:00+00:00\" status:\"closed\" AND severity:>\"MEDIUM\"" limit=3
Human Readable Output
Insights:
| Id | Readable Id | Name | Action | Status | Assignee | Description | Last Updated | Last Updated By | Severity | Closed | Closed By | Timestamp | Entity | Resolution |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 00853cdd-763e-3e31-a2e4-f74277922f9f | INSIGHT-220 | Command and Control with Defense Evasion and Execution | Closed | Initial Access, Command and Control, Defense Evasion, Execution | 2021-03-23T20:06:51.565599 | HIGH | 2021-03-23T20:06:51.511505 | obfuscated@email.com | 2021-02-22T16:27:51 | testcomputer.somedomain.net | No Action | |||
| eefdff8d-7447-3b47-83e0-66a0b210d618 | INSIGHT-219 | Discovery with Credential Access and Execution | Closed | Initial Access, Credential Access, Initial Access, Execution, Discovery, Credential Access | 2021-03-23T21:21:55.029798 | HIGH | 2021-03-23T21:21:54.914061 | obfuscated@email.com | 2021-02-22T16:24:07.959000 | 1.2.3.4 | No Action | |||
| 8a77d12e-5905-3401-ae7c-2e17b1fd3060 | INSIGHT-221 | Privilege Escalation with Persistence and Execution | Closed | obfuscated@email.com | Execution, Privilege Escalation, Persistence, Execution | 2021-05-12T21:47:08.297222 | HIGH | 2021-05-12T21:47:08.132251 | obfuscated@email.com | 2021-02-22T16:24:07.959000 | 5.6.7.8 | No Action |
sumologic-sec-signal-search
Search signals using available filters
Base Command
sumologic-sec-signal-search
Input
| Argument Name | Description | Required |
|---|---|---|
| query | Use a query string to search, see API documentation for more details. | Optional |
| created | When the Signal was created. Defaults to ‘All time’ if no time arguments are specified. Possible values are: All time, Last week, Last 48 hours, Last 24 hours. Default is All time. | Optional |
| contentType | Content type associated with the signals. Options: ANOMALY, DEFAULT, THREATINTEL, RULE. Possible values are: ANOMALY, DEFAULT, THREATINTEL, RULE. | Optional |
| offset | The number of items to skip before starting to collect the result set. Default is 0. | Optional |
| limit | The maximum number of items to return. Default is 10. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SumoLogicSec.SignalList.ContentType | String | Type of content that triggered the Signal |
| SumoLogicSec.SignalList.Description | String | Description of the Signal |
| SumoLogicSec.SignalList.Entity | String | Entity name associated with the Signal |
| SumoLogicSec.SignalList.Id | String | The ID of the Signal |
| SumoLogicSec.SignalList.Name | String | The name of the Signal |
| SumoLogicSec.SignalList.RecordCount | Number | Number of records associated with the Signal |
| SumoLogicSec.SignalList.RuleId | String | Rule ID associated with the Signal |
| SumoLogicSec.SignalList.Severity | Number | The severity of the Signal |
| SumoLogicSec.SignalList.Stage | String | The stage of the Signal |
| SumoLogicSec.SignalList.Suppressed | Boolean | Whether or not the Signal was suppressed |
| SumoLogicSec.SignalList.Timestamp | Date | Signal timestamp |
Command Example
!sumologic-sec-signal-search query="timestamp:NOW-7D.NOW name:contains(\"Internal\")"
Human Readable Output
Signals:
| Id | Name | Entity | Rule Id | Description | Severity | Stage | Timestamp | Content Type | Tags |
|---|---|---|---|---|---|---|---|---|---|
| b50fd570-341b-576d-85b5-8b5cd17c0aee | IP Address Scan - Internal | 1.2.3.4 | LEGACY-S00050 | A scan of IP addresses | 3 | Discovery | 2021-04-22T04:08:13.514000 | RULE | _mitreAttackTactic:TA0007, _mitreAttackTactic:TA0043, _mitreAttackTechnique:T1046, _mitreAttackTechnique:T1595 |
sumologic-sec-entity-search
Search entities using the available filters
Base Command
sumologic-sec-entity-search
Input
| Argument Name | Description | Required |
|---|---|---|
| query | Use a query string to search, see API documentation for more details. | Optional |
| ip | IP Address to search for e.g. 1.2.3.4. | Optional |
| hostname | Hostname to search for e.g. host.example.com. | Optional |
| username | Username to search for e.g. admin. | Optional |
| type | Entity type to search for. Options: username, hostname, ip, mac. Possible values are: username, hostname, ip, mac. | Optional |
| whitelisted | Is the Entity whitelisted? true/false. Possible values are: true, false. | Optional |
| tag | Tag contains value. | Optional |
| offset | The number of items to skip before starting to collect the result set. Default is 0. | Optional |
| limit | The maximum number of items to return. Default is 10. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SumoLogicSec.EntityList.ActivityScore | Number | Entity Activity Score |
| SumoLogicSec.EntityList.FirstSeen | Date | When the Entity was first seen |
| SumoLogicSec.EntityList.Id | String | Entity ID |
| SumoLogicSec.EntityList.IpHostname | String | Hostname associated with IP Entity |
| SumoLogicSec.EntityList.IsWhitelisted | Boolean | Whether or not the Entity is on allow list |
| SumoLogicSec.EntityList.LastSeen | Date | When the Entity was last seen |
| SumoLogicSec.EntityList.Name | String | The Entity name |
| SumoLogicSec.EntityList.OperatingSystem | String | Entity Operating System (observed or from inventory) |
| SumoLogicSec.EntityList.InventoryData | Boolean | Whether or not this Entity was ingested from inventory e.g. Active Directory |
| SumoLogicSec.EntityList.Hostname | String | Entity hostname |
| SumoLogicSec.EntityList.Department | String | Username Entity department |
| SumoLogicSec.EntityList.EmployeeId | String | Username Entity employee ID |
Command Example
!sumologic-sec-entity-search query="type:\"ip\" activityScore:>=3"
Human Readable Output
Entities:
| Id | Name | First Seen | Last Seen | Activity Score | Is Whitelisted | Operating System | Inventory Data |
|---|---|---|---|---|---|---|---|
| _ip-specops_analysis_lab-1.2.3.4 | 1.2.3.4 | 2021-04-22T04:08:13.514000 | 3 | false | false |
sumologic-sec-insight-set-status
Change status of Insight
Base Command
sumologic-sec-insight-set-status
Input
| Argument Name | Description | Required |
|---|---|---|
| insight_id | The insight to change status for. | Required |
| status | The desired Insight status. Possible values are: new, inprogress, closed. Default is in-progress. | Optional |
| resolution | Resolution for closing Insight. Valid values are: “Resolved”, “False Positive”, “No Action”, “Duplicate”. Possible values are: Resolved, False Positive, No Action, Duplicate. Default is Resolved. | Optional |
| sub_resolution | Custom sub resolution for closing Insight. If populated, it will override the resolution field. Please make sure the resolution matches exactly your Sumo Resolutions | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SumoLogicSec.Insight.Assignee | String | User or team assigned to the Insight |
| SumoLogicSec.Insight.Closed | Date | Closed date |
| SumoLogicSec.Insight.ClosedBy | String | Closed by user |
| SumoLogicSec.Insight.Created | Date | Created date |
| SumoLogicSec.Insight.Description | String | Description of the Insight |
| SumoLogicSec.Insight.Entity | String | Entity name associated with the Insight |
| SumoLogicSec.Insight.Id | String | The ID of the Insight |
| SumoLogicSec.Insight.LastUpdated | Date | The time the Insight was last updated |
| SumoLogicSec.Insight.LastUpdatedBy | String | The last user to update the Insight |
| SumoLogicSec.Insight.Name | String | The name of the Insight |
| SumoLogicSec.Insight.ReadableId | String | The ID of the Insight in readable form |
| SumoLogicSec.Insight.Resolution | String | Resolution for closed Insight |
| SumoLogicSec.Insight.Severity | String | The severity of the Insight |
| SumoLogicSec.Insight.Signals.contentType | String | Type of content that triggered the Signal |
| SumoLogicSec.Insight.Signals.description | String | Description of the Signal |
| SumoLogicSec.Insight.Signals.id | String | The ID of the Signal |
| SumoLogicSec.Insight.Signals.name | String | The name of the Signal |
| SumoLogicSec.Insight.Signals.recordCount | Number | Number of records associated with the Signal |
| SumoLogicSec.Insight.Signals.ruleId | String | Rule ID associated with the Signal |
| SumoLogicSec.Insight.Signals.severity | Number | The severity of the Signal |
| SumoLogicSec.Insight.Signals.stage | String | The stage of the Signal |
| SumoLogicSec.Insight.Signals.timestamp | Date | Signal timestamp |
| SumoLogicSec.Insight.Source | String | The source of the Insight |
| SumoLogicSec.Insight.Status | String | The status of the Insight |
| SumoLogicSec.Insight.TimeToDetection | Number | Insight time to detection |
| SumoLogicSec.Insight.TimeToRemediation | Number | Insight time to remediation |
| SumoLogicSec.Insight.TimeToResponse | Number | Insight time to response |
| SumoLogicSec.Insight.Timestamp | Date | Insight timestamp |
Command Example
!sumologic-sec-insight-set-status insight-id=INSIGHT-116 status=closed resolution="No Action"
Human Readable Output
Insight Details:
| Id | Readable Id | Name | Action | Status | Assignee | Description | Last Updated | Last Updated By | Severity | Closed | Closed By | Timestamp | Entity | Resolution |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| c6c97d84-983d-303e-a03b-86f53d657fc8 | INSIGHT-116 | Lateral Movement with Discovery and Credential Access | Closed | Initial Access, Lateral Movement, Discovery, Initial Access, Credential Access | 2021-05-13T01:28:32.648352 | HIGH | 2021-05-13T01:28:32.580039 | obfuscated@email.com | 2021-02-18T22:04:08.330000 | 1.2.3.4 | No Action |
sumologic-sec-match-list-get
Get match lists
Base Command
sumologic-sec-match-list-get
Input
| Argument Name | Description | Required |
|---|---|---|
| offset | The number of items to skip before starting to collect the result set. Default is 0. | Optional |
| limit | Number of match lists returned. Default is 10. | Optional |
| sort | Sort expression. Default is name. | Optional |
| sortDir | Sort direction. Possible values are: ASC, DESC. Default is ASC. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SumoLogicSec.MatchLists.Created | String | When the Match List was created |
| SumoLogicSec.MatchLists.CreatedBy | String | User that created the Match List |
| SumoLogicSec.MatchLists.DefaultTtl | Number | Default TTL for entries in the Match List |
| SumoLogicSec.MatchLists.Description | String | Description of the Match List |
| SumoLogicSec.MatchLists.Id | String | ID of the Match List |
| SumoLogicSec.MatchLists.LastUpdated | String | When the Match List was last updated |
| SumoLogicSec.MatchLists.LastUpdatedBy | String | The last user to update the Match List |
| SumoLogicSec.MatchLists.Name | String | Name of Match List |
| SumoLogicSec.MatchLists.TargetColumn | String | Match List Target Column |
Command Example
!sumologic-sec-match-list-get limit=3
Human Readable Output
Match lists:
| Id | Name | Target Column | Default Ttl |
|---|---|---|---|
| 173 | admin_ips | SrcIp | 0 |
| 24 | auth_servers | Ip | |
| 162 | auth_servers_dst | DstIp |
sumologic-sec-match-list-update
Add item to match list
Base Command
sumologic-sec-match-list-update
Input
| Argument Name | Description | Required |
|---|---|---|
| match_list_id | ID of match list. | Required |
| active | Item active or disabled. | Required |
| description | Description of match list item. | Required |
| expiration | Expiration of match list item, e.g. “2021-03-25T23:52:23.508Z”. | Required |
| value | Value of match list item. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SumoLogicSec.UpdateResult.Result | String | Result (Success or Failed) |
| SumoLogicSec.UpdateResult.Server response | Boolean | Server response (True or False) |
Command Example
!sumologic-sec-match-list-update match-list-id=166 description="My description" expiration=2021-04-25T22:36:10.925Z value="10.20.30.40" active=true
Human Readable Output
Result:
| Result | Server Response |
|---|---|
| Success | true |
sumologic-sec-threat-intel-search-indicators
Search Threat Intel Indicators
Base Command
sumologic-sec-threat-intel-search-indicators
Input
| Argument Name | Description | Required |
|---|---|---|
| q | Use a query string to search, see API documentation for more details. | Optional |
| value | The value to search for. | Required |
| offset | The number of items to skip before starting to collect the result set. Default is 0. | Optional |
| limit | The numbers of items to return. Default is 10. | Optional |
| sourceIds | Comma separated list of threat intelligence source IDs to search, e.g. 1,2,3. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SumoLogicSec.ThreatIntelIndicators.Active | Boolean | Whether or not the Threat Intel Indicator is Active |
| SumoLogicSec.ThreatIntelIndicators.Expiration | Date | Date and time the Threat Intel Indicator is set to expire |
| SumoLogicSec.ThreatIntelIndicators.Id | String | ID of Threat Intel Indicator |
| SumoLogicSec.ThreatIntelIndicators.Meta.created.username | String | User that created the Threat Intel Indicator |
| SumoLogicSec.ThreatIntelIndicators.Meta.created.when | Date | When the Threat Intel Indicator was created |
| SumoLogicSec.ThreatIntelIndicators.Meta.description | String | Description of Threat Intel Indicator |
| SumoLogicSec.ThreatIntelIndicators.Meta.updated | Date | When the Threat Intel Indicator was last updated |
| SumoLogicSec.ThreatIntelIndicators.Value | String | Value of Threat Intel Indicator |
Command Example
!sumologic-sec-threat-intel-search-indicators value=1.2.3.4 sourceIds=54
Human Readable Output
Threat Intel Indicators:
| Id | Value | Active | Expiration |
|---|---|---|---|
| f396ae69aa223c049ff639b3649ba1dd6465ec74397c3126916786bbcd6d76017468726561745f49705f44656d6973746f5f54657374 | 1.2.3.4 | true | 2021-04-29T00:00:00 |
sumologic-sec-threat-intel-get-sources
Get Threat Intel Sources
Base Command
sumologic-sec-threat-intel-get-sources
Input
| Argument Name | Description | Required |
|---|---|---|
| offset | The number of items to skip before starting to collect the result set. Default is 0. | Optional |
| limit | The numbers of items to return. Default is 10. | Optional |
| sort | Sort expression. Default is name. | Optional |
| sortDir | Sort direction. Possible values are: ASC, DESC. Default is ASC. | Optional |
Context Output
| Path | Type | Description |
|---|---|---|
| SumoLogicSec.ThreatIntelSources.Created | String | When the Threat Intel Source was created |
| SumoLogicSec.ThreatIntelSources.CreatedBy | String | User that created the Threat Intel Source |
| SumoLogicSec.ThreatIntelSources.Description | String | Description of Threat Intel Source |
| SumoLogicSec.ThreatIntelSources.Id | String | ID of Threat Intel Source |
| SumoLogicSec.ThreatIntelSources.LastUpdated | String | When the Threat Intel Source was last updated |
| SumoLogicSec.ThreatIntelSources.LastUpdatedBy | String | User that last updated the Threat Intel Source |
| SumoLogicSec.ThreatIntelSources.Name | String | Name of Threat Intel Source |
| SumoLogicSec.ThreatIntelSources.SourceType | String | Source type of Threat Intel Source |
Command Example
!sumologic-sec-threat-intel-get-sources limit=3
Human Readable Output
Threat intel sources:
| Id | Name | Description | Source Type |
|---|---|---|---|
| 35 | abuse.ch | CUSTOM | |
| 25 | Alienvault OTX | Alienvault | TAXII |
| 24 | Anomali | TAXII |
sumologic-sec-threat-intel-update-source
Add Threat Intel Indicator to Threat Intel Source
Base Command
sumologic-sec-threat-intel-update-source
Input
| Argument Name | Description | Required |
|---|---|---|
| threat-intel-source-id | ID of Threat Intel Source. | Required |
| active | Indicator active or disabled. Default is true. | Required |
| description | Description of indicator. | Required |
| expiration | Expiration of match list item, e.g. “2021-03-25T23:52:23.508Z”. | Required |
| value | Indicator value. | Required |
Context Output
| Path | Type | Description |
|---|---|---|
| SumoLogicSec.UpdateResult.Result | String | Result (Success or Failed) |
| SumoLogicSec.UpdateResult.Server response | Boolean | Server response (True or False) |
Command Example
!sumologic-sec-threat-intel-update-source threat-intel-source-id=54 active=true value=1.2.3.4 description=test expiration=2021-04-29T00:00:00.000Z
Human Readable Output
Result:
| Result | Server Response |
|---|---|
| Success | true |
Configuration parameters
api_endpoint— Sumo Logic API Endpoint (required)instance_endpoint— Sumo Logic Instance EndpointisFetch— Fetch incidentsincidentType— Incident typeaccess_id— Access ID (required)access_key— Access Key (required)incidentFetchInterval— Incidents Fetch Intervalmax_fetch— Fetch Limitfetch_query— Override default fetch queryfirst_fetch— First fetch timepull_signals— Pull associated Sumo Logic Signals as Incidentsmirror_direction— Incident Mirroring Directionclose_incident— Close Mirrored XSOAR Incidentclose_insight— Close Mirrored Sumo Logic Insightrecord_summary_fields— Override Record Summary Fields
Commands (14)
-
sumologic-sec-entity-get-detailsGet entity details for a specific entity ID.
-
sumologic-sec-entity-searchSearch entities using the available filters.
-
sumologic-sec-insight-add-commentAdd a comment for a specific Insight ID (Users can post and update comments on the Sumo Logic Cloud SIEM portal for any Insight ID).
-
sumologic-sec-insight-get-commentsGet comments for a specific Insight ID (Users can post and update comments on the Sumo Logic Cloud SIEM portal for any Insight ID).
-
sumologic-sec-insight-get-detailsGet Insight details for a specific Insight ID.
-
sumologic-sec-insight-searchSearch insights using available filters.
-
sumologic-sec-insight-set-statusChange the status of an Insight.
-
sumologic-sec-match-list-getGet match lists.
-
sumologic-sec-match-list-updateAdd an item to a match list.
-
sumologic-sec-signal-get-detailsGet Signal details for a specific Signal ID. Signal details command references signals in Sumo Logic Cloud SIEM which are created when records exhibit suspicious properties and mate with patterns or other detection logic.
-
sumologic-sec-signal-searchSearch signals using available filters.
-
sumologic-sec-threat-intel-get-sourcesGet Threat Intel Sources.
-
sumologic-sec-threat-intel-search-indicatorsSearch Threat Intel Indicators.
-
sumologic-sec-threat-intel-update-sourceAdd a Threat Intel Indicator to an Threat Intel Source.
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 """Integration for Sumo Logic Cloud SIEM """ import traceback from datetime import datetime from typing import Any, cast """ CONSTANTS """ MAX_INCIDENTS_TO_FETCH = 20 DEFAULT_HEADERS = {"Content-Type": "application/json"} DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ" # ISO8601 format with UTC, default in XSOAR DEFAULT_LOOKBACK_SECONDS = 300 # 5 minutes # =========== Mirroring Mechanism Globals =========== MIRROR_DIRECTION = {"None": None, "Incoming": "In", "Outgoing": "Out", "Incoming And Outgoing": "Both"} OUTGOING_MIRRORED_FIELDS = ["comment", "status"] XSOAR_SUMO_CLOSE_REASON_MAP = { "False Positive": "False Positive", "Duplicate": "Duplicate", "Resolved": "Resolved", "Other": "Resolved", } """ CLIENT CLASS """ class Client(BaseClient): """Client class to interact with the service API This Client implements API calls, and does not contain any XSOAR logic. Should only do requests and return data. """ def req(self, method, url_suffix, params=None, json_data=None, headers=None): """ Send the request to Sumo Logic and return the JSON response """ if headers is None: headers = DEFAULT_HEADERS json_data = {} if json_data is None else json_data r = self._http_request( headers=headers, method=method, params=params, json_data=json_data, url_suffix=url_suffix, resp_type="json" ).get("data") return r def set_extra_params(self, args: dict[str, Any]) -> None: """ Set any extra params (in the form of a dictionary) for this client """ self.extra_params = args def get_extra_params(self) -> dict[str, Any]: """ Set any extra params (in the form of a dictionary) for this client """ return self.extra_params """ HELPER FUNCTIONS """ def translate_severity(severity): """ Translate from Sumo Logic CSE insight severity to Demisto severity """ _severities = {"LOW": 1, "MEDIUM": 2, "HIGH": 3} return _severities.get(severity, 4) def add_to_query(q): if len(q) > 0: return f"{q} " # No need for 'AND' here else: return q def arg_time_query_to_q(q, argval, timefield): """ Convert created argument to querystring """ if not argval or argval == "All time": return q if argval == "Last week": return add_to_query(q) + f"{timefield}:NOW-7D..NOW" if argval == "Last 48 hours": return add_to_query(q) + f"{timefield}:NOW-48h..NOW" if argval == "Last 24 hours": return add_to_query(q) + f"{timefield}:NOW-24h..NOW" return None def add_list_to_q(q, fields, args): """ Add arguments to querystring """ for arg_field in fields: arg_value = args.get(arg_field, None) if arg_value: if "," in arg_value: quoted_values = [f'"{v}"' for v in arg_value.split(",")] q = add_to_query(q) + "{}:in({})".format(arg_field, ",".join(quoted_values)) else: q = add_to_query(q) + f'{arg_field}:"{arg_value}"' return q def insight_signal_to_readable(obj): """ Construct a readable json output from an original insight/signal object """ if obj is None: return {} # Capitalize fields cap_obj = {(k[0].capitalize() + k[1:]): v for k, v in obj.items()} # Only show Entity name (Insights and Signals) cap_obj["Entity"] = "" if obj.get("entity") and "name" in obj["entity"]: cap_obj["Entity"] = obj["entity"]["name"] # Only show status displayName (Insights only) if obj.get("status") and "displayName" in obj["status"]: cap_obj["Status"] = obj["status"]["displayName"] # For Assignee show username (email) cap_obj["Assignee"] = "" if obj.get("assignee") and "username" in obj["assignee"]: cap_obj["Assignee"] = obj["assignee"]["username"] # Remove some deprecated fields, replaced by "Assignee" cap_obj.pop("AssignedTo", None) cap_obj.pop("TeamAssignedTo", None) return cap_obj def entity_to_readable(obj): """ Readable json output from entity object """ if obj is None: return {} # Capitalize fields cap_obj = {(k[0].capitalize() + k[1:]): v for k, v in obj.items()} # For Entities, show 'OperatingSystem' if "Os" in cap_obj: cap_obj["OperatingSystem"] = cap_obj.pop("Os", None) else: cap_obj["OperatingSystem"] = None if len(cap_obj.get("Inventory", [])) > 0: invdata = cap_obj["Inventory"][0] if "metadata" in invdata and "operatingSystem" in invdata["metadata"]: cap_obj["OperatingSystem"] = invdata["metadata"]["operatingSystem"] cap_obj["InventoryData"] = True else: cap_obj["InventoryData"] = False cap_obj.pop("Inventory", None) # don't need to display data from inventory cap_obj.pop("Ip", None) # don't need to display Ip object return cap_obj def get_update_result(resp_json: bool): """ Readable json output from update """ return {"Result": "Success" if resp_json is True else "Failed", "Server Response": resp_json} def insight_timestamp_to_created_format(timestamp_int): """ Querying Insights using 'created' as opposed to 'timestamp' requires this conversion """ created_time = datetime.utcfromtimestamp(timestamp_int) return datetime.strftime(created_time, "%Y-%m-%dT%H:%M:%S.%f") def convert_timestampstr_to_epochms(timestampstr: str) -> int: """ Convert a Signal or Insight timestamp string to epoch millisecs """ try: incident_datetime = datetime.strptime(timestampstr, "%Y-%m-%dT%H:%M:%S.%f") except ValueError: incident_datetime = datetime.strptime(timestampstr, "%Y-%m-%dT%H:%M:%S") incident_created_time = int((incident_datetime - datetime.utcfromtimestamp(0)).total_seconds()) return incident_created_time * 1000 def craft_sumo_url(svc_url: str, resource_type: str, id: str) -> str: """ Craft a full URL to a Sumo Logic insight/signal based on its Id """ if resource_type == "insight": return f"{svc_url}/sec/insight/{id}" elif resource_type == "signal": return f"{svc_url}/sec/signal/{id}" else: return "" def is_inmirrorable_object(readable_remote_id: str) -> bool: """ Check if a remote object ID is mirrorable into XSOAR. Currently on Sumo Logic Insights can be in-mirrored into XSOAR. Note the readable_remote_id must be in reable form, not the raw ID """ return bool(readable_remote_id.startswith("INSIGHT")) """ COMMAND FUNCTIONS """ def test_module(client: Client) -> str: """Tests API connectivity and authentication' Returning 'ok' indicates that the integration works like it is supposed to. Connection to the service is successful. Raises exceptions if something goes wrong. :type client: ``Client`` :param Client: client to use :return: 'ok' if test passed, anything else will fail the test. :rtype: ``str`` """ message: str = "" try: # test client and auth client.req("GET", "sec/v1/insights", {}) # test fetch_incidents command first_fetch_time = arg_to_datetime( arg="1 day", # using '1 day' here since we're just testing connectivity and auth arg_name="First fetch time", ) first_fetch_timestamp = int(first_fetch_time.timestamp()) if first_fetch_time else None # Using assert as a type guard (since first_fetch_time is always an int when required=True) assert isinstance(first_fetch_timestamp, int) fetch_incidents( client=client, max_results=20, last_run={}, # getLastRun() gets the last run dict first_fetch_time=first_fetch_timestamp, fetch_query="", # defaults to status:in("new", "inprogress") pull_signals=False, record_summary_fields="", other_args=None, ) message = "ok" except DemistoException as e: if "Unauthorized" in str(e): message = "Authorization Error: make sure Access ID and Access Key are correctly set" else: raise e return message def insight_get_details(client: Client, args: dict[str, Any]) -> CommandResults: """ Get insight details """ insight_id = args.get("insight_id") if not insight_id: raise ValueError("insight_id not specified") record_summary_fields = args.get("record_summary_fields") query = {} query["exclude"] = "signals.allRecords" if record_summary_fields: query["recordSummaryFields"] = record_summary_fields resp_json = client.req("GET", f"sec/v1/insights/{insight_id}", query) insight = insight_signal_to_readable(resp_json) insight["SumoUrl"] = craft_sumo_url(client.get_extra_params()["instance_endpoint"], "insight", insight_id) readable_output = tableToMarkdown( "Insight Details:", [insight], [ "Id", "ReadableId", "Name", "Action", "Status", "Assignee", "Description", "LastUpdated", "LastUpdatedBy", "Severity", "Closed", "ClosedBy", "Timestamp", "Entity", "Resolution", "SumoUrl", ], headerTransform=pascalToSpace, ) return CommandResults( readable_output=readable_output, outputs_prefix="SumoLogicSec.Insight", outputs_key_field="Id", outputs=insight ) def insight_add_comment(client: Client, args: dict[str, Any]) -> CommandResults: """ Add a comment to an insight """ insight_id = args.get("insight_id") reqbody = {} reqbody["body"] = args.get("comment") c = client.req("POST", f"sec/v1/insights/{insight_id}/comments", None, reqbody) comment = [ { "Id": c.get("id"), "Body": c.get("body"), "Author": c.get("author").get("username"), "Timestamp": c.get("timestamp"), "InsightId": insight_id, } ] readable_output = tableToMarkdown( "Insight Added Comment:", comment, ["Id", "InsightId", "Author", "Body", "Timestamp"], headerTransform=pascalToSpace ) return CommandResults( readable_output=readable_output, outputs_prefix="SumoLogicSec.InsightComments", outputs_key_field="Id", outputs=comment ) def insight_get_comments(client: Client, args: dict[str, Any]) -> CommandResults: """ Get comments for insight """ insight_id = args.get("insight_id") resp_json = client.req("GET", f"sec/v1/insights/{insight_id}/comments") comments = [ { "Id": c.get("id"), "Body": c.get("body"), "Author": c.get("author").get("username"), "Timestamp": c.get("timestamp"), "InsightId": insight_id, } for c in resp_json.get("comments") ] readable_output = tableToMarkdown( "Insight Comments:", comments, ["Id", "InsightId", "Author", "Body", "LastUpdated", "Timestamp"], headerTransform=pascalToSpace, ) return CommandResults( readable_output=readable_output, outputs_prefix="SumoLogicSec.InsightComments", outputs_key_field="Id", outputs=comments ) def signal_get_details(client: Client, args: dict[str, Any]) -> CommandResults: """ Get signal details """ signal_id = args.get("signal_id", None) if not signal_id: raise ValueError("signal_id not specified") signal = client.req("GET", f"sec/v1/signals/{signal_id}") signal.pop("allRecords", None) # don't need to display records from signal signal = insight_signal_to_readable(signal) signal["SumoUrl"] = craft_sumo_url(client.get_extra_params()["instance_endpoint"], "signal", signal_id) readable_output = tableToMarkdown( "Signal Details:", [signal], ["Id", "Name", "RuleId", "Description", "Severity", "ContentType", "Timestamp", "Entity", "SumoUrl"], headerTransform=pascalToSpace, ) return CommandResults( readable_output=readable_output, outputs_prefix="SumoLogicSec.Signal", outputs_key_field="Id", outputs=signal ) def entity_get_details(client: Client, args: dict[str, Any]) -> CommandResults: """ Get entity details """ entity_id = args.get("entity_id", None) if not entity_id: raise ValueError("entity_id not specified") resp_json = client.req("GET", f"sec/v1/entities/{entity_id}", {"expand": "inventory"}) entity = entity_to_readable(resp_json) readable_output = tableToMarkdown( "Entity Details:", [entity], ["Id", "Name", "FirstSeen", "LastSeen", "ActivityScore", "IsWhitelisted", "OperatingSystem", "InventoryData"], headerTransform=pascalToSpace, ) return CommandResults( readable_output=readable_output, outputs_prefix="SumoLogicSec.Entity", outputs_key_field="Id", outputs=entity ) def insight_search(client: Client, args: dict[str, Any]) -> CommandResults: """ Search insights using available filters The search query string is a custom DSL that is used to filter the results. Operators: - `exampleField:"bar"`: The value of the field is equal to "bar". - `exampleField:in("bar", "baz", "qux")`: The value of the field is equal to either "bar", "baz", or "qux". - `exampleTextField:contains("foo bar")`: The value of the field contains the phrase "foo bar". - `exampleNumField:>5`: The value of the field is greater than 5. There are similar `<`, `<=`, and `>=` operators. - `exampleNumField:5..10`: The value of the field is between 5 and 10 (inclusive). - `exampleDateField:>2019-02-01T05:00:00+00:00`: The value of the date field is after 5 a.m. UTC time on February 2, 2019. - `exampleDateField:2019-02-01T05:00:00+00:00..2019-02-01T08:00:00+00:00`: The value of the date field is between 5 a.m. and 8 a.m. UTC time on February 2, 2019. Fields: - id - readableId - status - name - insightId - description - created - timestamp - closed - assignee - entity.ip - entity.hostname - entity.username - entity.type - enrichment - tag - severity - resolution - ruleId - records For example, the query `timestamp:>2021-03-18T12:00:00+00:00 severity:"HIGH` will return insights of high severity created after 12 PM UTC time on March 18th, 2021. """ record_summary_fields = args.get("record_summary_fields") query = {} q = args.get("query", "") q = arg_time_query_to_q(q, args.get("created"), "created") q = add_list_to_q(q, ["status", "assignee"], args) query["q"] = q query["offset"] = args.get("offset") query["limit"] = args.get("limit") query["exclude"] = "signals.allRecords" if record_summary_fields: query["recordSummaryFields"] = record_summary_fields resp_json = client.req("GET", "sec/v1/insights", query) insights = [] for insight in resp_json.get("objects"): insights.append(insight_signal_to_readable(insight)) readable_output = tableToMarkdown( "Insights:", insights, [ "Id", "ReadableId", "Name", "Action", "Status", "Assignee", "Description", "LastUpdated", "LastUpdatedBy", "Severity", "Closed", "ClosedBy", "Timestamp", "Entity", "Resolution", ], headerTransform=pascalToSpace, ) return CommandResults( readable_output=readable_output, outputs_prefix="SumoLogicSec.InsightList", outputs_key_field="Id", outputs=insights ) def entity_search(client: Client, args: dict[str, Any]) -> CommandResults: """ Search entities using the available filters """ query = {} q = args.get("query", "") q = add_list_to_q(q, ["ip", "hostname", "username", "type", "whitelisted", "tag"], args) query["q"] = q query["offset"] = args.get("offset") query["limit"] = args.get("limit") query["sort_by"] = args.get("sort") query["expand"] = "inventory" resp_json = client.req("GET", "sec/v1/entities", query) entities = [] for entity in resp_json.get("objects"): entities.append(entity_to_readable(entity)) readable_output = tableToMarkdown( "Entities:", entities, ["Id", "Name", "FirstSeen", "LastSeen", "ActivityScore", "IsWhitelisted", "OperatingSystem", "InventoryData"], headerTransform=pascalToSpace, ) return CommandResults( readable_output=readable_output, outputs_prefix="SumoLogicSec.EntityList", outputs_key_field="Id", outputs=entities ) def signal_search(client: Client, args: dict[str, Any]) -> CommandResults: """ Search signals using available filters """ query = {} q = args.get("query", "") q = arg_time_query_to_q(q, args.get("created"), "created") q = add_list_to_q(q, ["category", "contentType"], args) query["q"] = q query["offset"] = args.get("offset") query["limit"] = args.get("limit") resp_json = client.req("GET", "sec/v1/signals", query) signals = [] for signal in resp_json.get("objects"): signal.pop("allRecords", None) # don't need to display records from signal signals.append(insight_signal_to_readable(signal)) readable_output = tableToMarkdown( "Signals:", signals, ["Id", "Name", "Entity", "RuleId", "Description", "Severity", "Stage", "Timestamp", "ContentType", "Tags"], headerTransform=pascalToSpace, ) return CommandResults( readable_output=readable_output, outputs_prefix="SumoLogicSec.SignalList", outputs_key_field="Id", outputs=signals ) def insight_set_status(client: Client, args: dict[str, Any]) -> CommandResults: """ Change status of insight Provide "reason" arg when closing an Insight with status=closed. """ insight_id = args.get("insight_id") reqbody = {} reqbody["status"] = args.get("status") resolution = args.get("sub_resolution") or args.get("resolution") if args.get("status") == "closed" and resolution: # resolution should only be specified when the status is set to "closed" reqbody["resolution"] = resolution resp_json = client.req("PUT", f"sec/v1/insights/{insight_id}/status", None, reqbody) for s in resp_json.get("signals"): s.pop("allRecords", None) insight = insight_signal_to_readable(resp_json) readable_output = tableToMarkdown( "Insight Details:", [insight], [ "Id", "ReadableId", "Name", "Action", "Status", "Assignee", "Description", "LastUpdated", "LastUpdatedBy", "Severity", "Closed", "ClosedBy", "Timestamp", "Entity", "Resolution", ], headerTransform=pascalToSpace, ) return CommandResults( readable_output=readable_output, outputs_prefix="SumoLogicSec.Insight", outputs_key_field="Id", outputs=insight ) def match_list_get(client: Client, args: dict[str, Any]) -> CommandResults: """ Get match lists """ query = {} query["offset"] = args.get("offset") query["limit"] = args.get("limit") query["sort"] = args.get("sort") query["sortDir"] = args.get("sortDir") resp_json = client.req("GET", "sec/v1/match-lists", query) match_lists = [] for match_list in resp_json.get("objects"): cap_match_list = {(k[0].capitalize() + k[1:]): v for k, v in match_list.items()} match_lists.append(cap_match_list) readable_output = tableToMarkdown( "Match lists:", match_lists, headers=["Id", "Name", "TargetColumn", "DefaultTtl"], headerTransform=pascalToSpace ) # Filtered out from readable output: 'Description', 'Created', 'CreatedBy', 'LastUpdated', 'LastUpdatedBy' return CommandResults( readable_output=readable_output, outputs_prefix="SumoLogicSec.MatchLists", outputs_key_field="Id", outputs=match_lists ) def match_list_update(client: Client, args: dict[str, Any]) -> CommandResults: """ Add to match list """ match_list_id = args.get("match_list_id") item = {} item["active"] = args.get("active") item["description"] = args.get("description") item["expiration"] = args.get("expiration") item["value"] = args.get("value") resp_json = client.req("POST", f"sec/v1/match-lists/{match_list_id}/items", None, {"items": [item]}) result = get_update_result(resp_json) readable_output = tableToMarkdown("Result:", [result], ["Result", "Server Response"]) return CommandResults(readable_output=readable_output, outputs_prefix="SumoLogicSec.UpdateResult", outputs=result) def threat_intel_search_indicators(client: Client, args: dict[str, Any]) -> CommandResults: """ Search Threat Intel Indicators The search query string in our custom DSL that is used to filter the results. Operators: - `exampleField:"bar"`: The value of the field is equal to "bar". - `exampleField:in("bar", "baz", "qux")`: The value of the field is equal to either "bar", "baz", or "qux". - `exampleTextField:contains("foo bar")`: The value of the field contains the phrase "foo bar". - `exampleNumField:>5`: The value of the field is greater than 5. There are similar `<`, `<=`, and `>=` operators. - `exampleNumField:5..10`: The value of the field is between 5 and 10 (inclusive). - `exampleDateField:>2019-02-01T05:00:00+00:00`: The value of the date field is after 5 a.m. UTC time on February 2, 2019. - `exampleDateField:2019-02-01T05:00:00+00:00..2019-02-01T08:00:00+00:00`: The value of the date field is between 5 a.m. and 8 a.m. UTC time on February 2, 2019. Fields: - id - targetColumn - value - active - expirationDate - listName - description - created """ query = {} if args.get("query"): query["q"] = args.get("query") query["value"] = args.get("value") if args.get("sourceIds"): query["sourceIds"] = args["sourceIds"].split(",") query["offset"] = args.get("offset") query["limit"] = args.get("limit") resp_json = client.req("GET", "sec/v1/threat-intel-indicators", query) indicators = [] for indicator in resp_json.get("objects"): cap_indicator = {(k[0].capitalize() + k[1:]): v for k, v in indicator.items()} indicators.append(cap_indicator) readable_output = tableToMarkdown("Threat Intel Indicators:", indicators, ["Id", "Value", "Active", "Expiration"]) # Filtered out from readable output: Meta return CommandResults( readable_output=readable_output, outputs_prefix="SumoLogicSec.ThreatIntelIndicators", outputs_key_field="Id", outputs=indicators, ) def threat_intel_get_sources(client: Client, args: dict[str, Any]) -> CommandResults: """ Get the list of Threat Intel Sources """ query = {} query["offset"] = args.get("offset") query["limit"] = args.get("limit") query["sort"] = args.get("sort") query["sortDir"] = args.get("sortDir") resp_json = client.req("GET", "sec/v1/threat-intel-sources", query) threat_intel_sources = [] for threat_intel_source in resp_json.get("objects"): cap_threat_intel_source = {(k[0].capitalize() + k[1:]): v for k, v in threat_intel_source.items()} threat_intel_sources.append(cap_threat_intel_source) readable_output = tableToMarkdown( "Threat intel sources:", threat_intel_sources, headers=["Id", "Name", "Description", "SourceType"], headerTransform=pascalToSpace, ) # Filtered out from readable output: Created', 'CreatedBy', 'LastUpdated', 'LastUpdatedBy' return CommandResults( readable_output=readable_output, outputs_prefix="SumoLogicSec.ThreatIntelSources", outputs_key_field="Id", outputs=threat_intel_sources, ) def threat_intel_update_source(client: Client, args: dict[str, Any]) -> CommandResults: """ Add Indicator to a Threat Intel Source """ threat_intel_source_id = args.get("threat_intel_source_id") item = {} item["active"] = args.get("active") item["description"] = args.get("description") item["expiration"] = args.get("expiration") item["value"] = args.get("value") resp_json = client.req("POST", f"sec/v1/threat-intel-sources/{threat_intel_source_id}/items", None, {"indicators": [item]}) result = get_update_result(resp_json) readable_output = tableToMarkdown("Result:", [result], ["Result", "Response"]) return CommandResults(readable_output=readable_output, outputs_prefix="SumoLogicSec.UpdateResult", outputs=result) def cleanup_records(signal: Optional[dict[str, Any]]) -> Optional[dict[str, Any]]: """ Function to clean up all "bro" fields of the records under a Signal object """ if (signal is None) or ("allRecords" not in signal): return None for rec in signal["allRecords"]: field_names = list(rec.keys()) for field_name in field_names: if field_name.startswith("bro") and rec.get(field_name) in ([], (), {}, ""): rec.pop(field_name, None) if field_name == "timestamp": rec["lastlog_timestamp"] = rec["timestamp"] rec.pop(field_name, None) return signal def get_remote_data_command(client: Client, args: dict, close_incident: bool): """get-remote-data command: Returns an updated Sumo Logic Cloud SIEM Insight incident Args: client: Client object to call a Sumo Logic SIEM args (dict): The command arguments close_incident (bool): Whether to close the corresponding XSOAR incident if the Sumo Logic SIEM Insight has been closed Returns: GetRemoteDataResponse: The Response containing the update to mirror and the entries """ entries = [] remote_args = GetRemoteDataArgs(args) last_update = remote_args.last_update # last_update_utc = dateparser.parse(last_update, settings={'TIMEZONE': 'UTC'}) insight_id = remote_args.remote_incident_id demisto.debug(f"Get-Remote-Data-Command for {insight_id} {last_update}") if not is_inmirrorable_object(insight_id): demisto.debug(f"Not in-mirrorable object with {insight_id}") return GetRemoteDataResponse(mirrored_object={}, entries={}) else: insight = insight_get_details(client, {"insight_id": insight_id}).outputs insight_resolution = insight.get("Resolution") # type: ignore if insight["Status"] == "Closed" and close_incident: # type: ignore resolution = insight_resolution if resolution == "No Action": resolution = "Other" demisto.info(f"Closing incident related to Sumo Logic Insight {insight_id} with resolution {insight_resolution}, \ which is mapped to XSOAR reason: {resolution}") entries = [ { "Type": EntryType.NOTE, "Contents": { "dbotIncidentClose": True, "closeReason": resolution, "closeNotes": f"Insight {insight_id} was closed on Sumo Logic SIEM with resolution {insight_resolution}, \ which is mapped to XSOAR reason: {resolution}.", }, "ContentsFormat": EntryFormat.JSON, } ] demisto.debug(f"Updated Sumo Logic Insight {insight_id}") return GetRemoteDataResponse(mirrored_object=insight, entries=entries) def update_remote_system_command(client: Client, args: dict[str, Any], params: dict[str, Any]) -> str: """Pushes changes in XSOAR incident into the corresponding Sumo Logic Insight. Args: args (dict): Demisto args params (dict): Demisto params client: Client to connect to Sumo Logic SIEM Returns: insight_id (str): The Sumo Logic Insight Id """ parsed_args = UpdateRemoteSystemArgs(args) delta = parsed_args.delta # Insight ID on Sumo Logic SIEM side insight_id = parsed_args.remote_incident_id # Incident ID on XSOAR side incident_id = parsed_args.data["id"] if not is_inmirrorable_object(insight_id): demisto.info(f"Not an Insight incident: {insight_id} so won't mirror") return insight_id if parsed_args.incident_changed and delta: demisto.debug(f"Got the following delta keys {list(delta.keys())!s} to update incident \ corresponding to Insight {insight_id}") demisto.debug(f"Got the following delta {delta} to update incident corresponding to Insight {insight_id}") demisto.debug(f"Incident Id: {incident_id}") changed_data = {field: "" for field in OUTGOING_MIRRORED_FIELDS} for field in delta: if field in OUTGOING_MIRRORED_FIELDS: changed_data[field] = delta[field] if "closeReason" in delta: if parsed_args.inc_status == IncidentStatus.ACTIVE: changed_data["status"] = "inprogress" if parsed_args.inc_status == IncidentStatus.PENDING: changed_data["status"] = "new" # Close Insight if relevant if parsed_args.inc_status == IncidentStatus.DONE and params.get("close_insight"): demisto.debug(f"Closing Sumo Logic Insight {insight_id}") changed_data["status"] = "closed" # XSOAR has by default: False Positive, Duplicate, Resolved and Other which can be # mapped directly to Sumo Logic default resolutions (except for Other). For any custom # resolution, there must be a 1-1 mapping between Sumo SIEM and XSOAR side. reason = delta["closeReason"] if reason == "Other": reason = "Resolved" changed_data["resolution"] = reason changed_data["insight_id"] = insight_id demisto.debug(f"Sending update status request to Sumo Logic for Insight {insight_id}, data: {changed_data}") insight_add_comment( client, { "insight_id": insight_id, "comment": f"Close since the corresponding XSOAR Insight incident: {incident_id} was closed", }, ) insight_obj = insight_set_status(client, changed_data).outputs # type: ignore return insight_obj.get("ReadableId") # type: ignore else: demisto.debug(f"Incident corresponding to Sumo Logic Insight {insight_id} was not changed.") return insight_id def get_modified_remote_data_command(client: Client, args: Any) -> Any: """Gets all Sumo Logic Insights that have changed since a given time. Currently not used since Sumo Logic API does not allow filtering insights by update time Args: client: Client object to call a Sumo Logic SIEM args (dict): The command arguments Returns: GetModifiedRemoteDataResponse: The response containing the list of ids of Insights changed """ raise NotImplementedError("get-modified-remote-data not implemented") def fetch_incidents( client: Client, max_results: int, last_run: dict[str, Any], first_fetch_time: Optional[int], fetch_query: Optional[str], pull_signals: Optional[bool], record_summary_fields: Optional[str], other_args: dict[str, Any] | None, ) -> tuple[dict[str, Any], list[dict]]: """ Retrieve new incidents periodically based on pre-defined instance parameters. Implements a lookback window and deduplication to avoid missing delayed insights. """ # Get the last fetch time, if exists # last_run is a dict with a single key, called last_fetch demisto.debug(f"Sumo Logic Integration last run: {last_run}") last_fetch = last_run.get("last_fetch", None) # Track IDs from previous run for deduplication during lookback window overlap last_seen_ids = last_run.get("last_fetch_ids", []) last_seen_ids = set(last_seen_ids) if last_seen_ids else set() # convert to set for O(1) deduplication lookup current_fetch_ids: list[str] = [] # Set lookback seconds based on whether this is first run lookback_seconds = 0 if last_fetch is None else DEFAULT_LOOKBACK_SECONDS if last_fetch is None: # First run: no lookback, use provided first_fetch_time if first_fetch_time is None: raise ValueError("first_fetch_time must be provided on first run") fetch_start_time = int(first_fetch_time) # Store first_fetch_time to persist across cycles stored_first_fetch_time = fetch_start_time else: # Subsequent runs: apply lookback window to catch delayed insights last_fetch = int(last_fetch) fetch_start_time = last_fetch - lookback_seconds # Retrieve stored first_fetch_time or initialize it for backwards compatibility # For existing instances without stored value, use current fetch_start_time as the floor # This value will be persisted to prevent infinite backward regression stored_first_fetch_time = last_run.get("first_fetch_time", fetch_start_time) # Cap fetch_start_time to never go below the original first_fetch_time # This prevents infinite backward regression when hitting fetch limits fetch_start_time = max(fetch_start_time, stored_first_fetch_time) # Throw error if fetch_start_time is negative to prevent querying from Unix epoch if fetch_start_time <= 0: raise ValueError( f"Calculated fetch_start_time ({fetch_start_time}) is negative. " f"last_fetch={last_fetch}, lookback_seconds={lookback_seconds}" ) # Track the latest timestamp for next run's last_fetch latest_created_time = last_fetch if last_fetch else fetch_start_time # Initialize an empty list of incidents to return # Each incident is a dict with a string as a key incidents: list[dict[str, Any]] = [] # Set query values that do not change with pagination q = f"created:>={insight_timestamp_to_created_format(fetch_start_time)}" offset = 0 # ensure consistent ordering for pagination, even if new insights are created during the fetch process query = { "sort": "CREATED", "sortDir": "ASC", } if fetch_query: query["q"] = q + " " + fetch_query else: query["q"] = q + ' status:in("new", "inprogress")' query["limit"] = str(max_results) if record_summary_fields: query["recordSummaryFields"] = record_summary_fields hasNextPage = True instance_endpoint = client.get_extra_params()["instance_endpoint"] signal_ids = [] counter = 0 # Retrieve Insights while hasNextPage and counter < max_results: # only query parameter that changes loop to loop is the offset query["offset"] = str(offset) resp_json = client.req("GET", "sec/v1/insights", query) if not resp_json: break for insight in resp_json.get("objects", []): # If no created_time set is as epoch (0). We use time in ms so we must # convert it from the API response insight_timestamp = insight.get("created") insight_id = insight.get("id") insight_readableid = insight.get("readableId") # add sumoUrl to raw insight: insight["sumoUrl"] = craft_sumo_url(instance_endpoint, "insight", insight_id) if other_args is not None: insight["mirror_instance"] = other_args["mirror_instance"] insight["mirror_direction"] = other_args["mirror_direction"] # Skip already processed insights to prevent duplicates during lookback overlap if insight_id and insight_timestamp and insight_id not in last_seen_ids: incident_created_time_ms = convert_timestampstr_to_epochms(insight_timestamp) incident_created_time = int(incident_created_time_ms / 1000) # Process signals associated with this insight signals = insight.get("signals", []) for signal in signals: # add sumoUrl to signal: signal_id = signal["id"] signal_ids.append(signal_id) signal["sumoUrl"] = craft_sumo_url(instance_endpoint, "signal", signal_id) cleanup_records(signal) # Create incident incidents.append( { "name": insight.get("name", "No name") + " - " + insight_readableid, "occurred": timestamp_to_datestring(incident_created_time_ms), "details": insight.get("description"), "severity": translate_severity(insight.get("severity")), "rawJSON": json.dumps(insight), } ) counter += 1 # Update latest timestamp if this insight is newer if incident_created_time > latest_created_time: latest_created_time = incident_created_time # Track this insight ID for deduplication in the next run if it's within the lookback window current_fetch_ids.append(insight_id) if not resp_json.get("hasNextPage"): hasNextPage = False else: offset += len(resp_json.get("objects", [])) final_incidents = [] if pull_signals: # Retrieve Signals associated with the insights signal_query = {} i = 0 batch_size = 10 signal_incidents = [] while i < len(signal_ids): signal_list_str = ",".join([f'"{x}"' for x in signal_ids[i : i + batch_size]]) signal_query["q"] = f"id:in({signal_list_str})" resp_json = client.req("GET", "sec/v1/signals", signal_query) if not resp_json: break for signal in resp_json.get("objects", []): signal_id = signal.get("id") # add sumoUrl to signal: signal["sumoUrl"] = craft_sumo_url(instance_endpoint, "signal", signal_id) # field inserted for classifier signal["readableId"] = "SIGNAL-" + signal_id cleanup_records(signal) signal_created_time_ms = convert_timestampstr_to_epochms(signal["timestamp"]) signal_incidents.append( { "name": signal.get("name", "No name") + " - " + signal_id, "occurred": timestamp_to_datestring(signal_created_time_ms), "details": signal.get("description"), "severity": translate_severity(signal.get("severity")), "rawJSON": json.dumps(signal), } ) if other_args is not None: signal["mirror_instance"] = other_args["mirror_instance"] signal["mirror_direction"] = other_args["mirror_direction"] i += batch_size # Append incidents to the signal list so the signals will be created first: final_incidents.extend(signal_incidents) del signal_incidents final_incidents.extend(incidents) del incidents # Save the next_run as a dict with the last_fetch and last_seen_ids keys to be stored next_run = cast( dict[str, Any], { "last_fetch": latest_created_time, "last_fetch_ids": current_fetch_ids if len(current_fetch_ids) > 0 else list(last_seen_ids), "first_fetch_time": stored_first_fetch_time, }, ) return next_run, final_incidents """ MAIN FUNCTION """ def main() -> None: # pragma: no cover """main function, parses params and runs command functions :return: :rtype: """ proxy = demisto.params().get("proxy", False) base_url = demisto.getParam("api_endpoint") access_id = demisto.getParam("access_id") access_key = demisto.getParam("access_key") verify_certificate = not demisto.params().get("insecure", False) # How much time before the first fetch to retrieve incidents first_fetch_time = arg_to_datetime( arg=demisto.params().get("first_fetch", "1 day"), arg_name="First fetch time", required=True ) first_fetch_timestamp = int(first_fetch_time.timestamp()) if first_fetch_time else None # Using assert as a type guard (since first_fetch_time is always an int when required=True) assert isinstance(first_fetch_timestamp, int) fetch_query = demisto.getParam("fetch_query") record_summary_fields = demisto.getParam("record_summary_fields") pull_signals = demisto.getParam("pull_signals") other_args = { "mirror_instance": demisto.integrationInstance(), "mirror_direction": MIRROR_DIRECTION.get(demisto.params().get("mirror_direction")), } command = demisto.command() demisto.debug(f"Command being called is {command}") try: client = Client( base_url=base_url, verify=verify_certificate, headers=DEFAULT_HEADERS, proxy=proxy, auth=(access_id, access_key), ok_codes=[200], ) client.set_extra_params({"instance_endpoint": demisto.getParam("instance_endpoint")}) if command == "test-module": # This is the call made when pressing the integration Test button. result = test_module(client) return_results(result) elif command == "sumologic-sec-insight-get-details": return_results(insight_get_details(client, demisto.args())) elif command == "sumologic-sec-insight-get-comments": return_results(insight_get_comments(client, demisto.args())) elif command == "sumologic-sec-insight-add-comment": return_results(insight_add_comment(client, demisto.args())) elif command == "sumologic-sec-signal-get-details": return_results(signal_get_details(client, demisto.args())) elif command == "sumologic-sec-entity-get-details": return_results(entity_get_details(client, demisto.args())) elif command == "sumologic-sec-insight-search": return_results(insight_search(client, demisto.args())) elif command == "sumologic-sec-entity-search": return_results(entity_search(client, demisto.args())) elif command == "sumologic-sec-signal-search": return_results(signal_search(client, demisto.args())) elif command == "sumologic-sec-insight-set-status": return_results(insight_set_status(client, demisto.args())) elif command == "sumologic-sec-match-list-get": return_results(match_list_get(client, demisto.args())) elif command == "sumologic-sec-match-list-update": return_results(match_list_update(client, demisto.args())) elif command == "sumologic-sec-threat-intel-search-indicators": return_results(threat_intel_search_indicators(client, demisto.args())) elif command == "sumologic-sec-threat-intel-get-sources": return_results(threat_intel_get_sources(client, demisto.args())) elif command == "sumologic-sec-threat-intel-update-source": return_results(threat_intel_update_source(client, demisto.args())) elif command == "update-remote-system": demisto.info("########### MIRROR OUT FROM XSOAR #############") update_remote_system_command(client, demisto.args(), demisto.params()) elif command == "get-remote-data": demisto.info("########### MIRROR INTO XSOAR #############") return_results(get_remote_data_command(client, demisto.args(), other_args["mirror_direction"] is not None)) elif command == "get-modified-remote-data": return_results(get_modified_remote_data_command(client, demisto.args())) elif command == "fetch-incidents": # Convert the argument to an int using helper function or set to MAX_INCIDENTS_TO_FETCH max_results = arg_to_number(arg=demisto.params().get("max_fetch"), arg_name="max_fetch", required=False) if not max_results or max_results > MAX_INCIDENTS_TO_FETCH: max_results = MAX_INCIDENTS_TO_FETCH next_run, incidents = fetch_incidents( client=client, max_results=max_results, last_run=demisto.getLastRun(), # getLastRun() gets the last run dict first_fetch_time=first_fetch_timestamp, fetch_query=fetch_query, pull_signals=pull_signals, record_summary_fields=record_summary_fields, other_args=other_args, ) # saves next_run for the time fetch-incidents is invoked demisto.setLastRun(next_run) # fetch-incidents calls ``demisto.incidents()`` to provide the list # of incidents to create demisto.incidents(incidents) # Log exceptions and return errors except Exception as e: demisto.error(traceback.format_exc()) # print the traceback return_error(f"Failed to execute {demisto.command()} command.\nError:\n{e!s}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()