TaegisXDR Deprecated
Deprecated. Use TaegisXDR v2 instead.
Data Enrichment & Threat Intelligence · Secureworks
Details
| ID | TaegisXDR |
|---|---|
| Provider | Sophos |
| Category | Data Enrichment & Threat Intelligence |
| From Version | 5.5.0 |
| Docker Image | demisto/python3:3.10.12.65389 |
| Supported Modules | Agentix XSIAM |
README
Configure Taegis XDR in Cortex
| Parameter | Description | Required |
|---|---|---|
| Taegis Environment | The environment to utilize | True |
| Client ID | Client ID as described in the Taegis Documentation | True |
| Client Secret | Client Secret as described in the Taegis Documentation | True |
| Use system proxy settings | Defines whether the system proxy is used or not | False |
Commands
You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.
taegis-archive-investigation
Base Command
!taegis-archive-investigation
Inputs
| Argument Name | Description | Required |
|---|---|---|
| id | The investigation id to archive | True |
Command Example
!taegis-archive-investigation id=c207ca4c-8a78-4408-a056-49f05d6eb77d
Context Example
{
"TaegisXDR": {
"ArchivedInvestigation": {
"id": "c207ca4c-8a78-4408-a056-49f05d6eb77d"
}
}
}
taegis-create-comment
Base Command
!taegis-create-comment
Inputs
| Argument Name | Description | Required |
|---|---|---|
| comment | The comment string to add to the investigation | True |
| parent_id | The investigation ID to add the comment to | True |
Command Example
!taegis-create-comment comment="This is a test comment" parent_id="219da0ee-8642-4363-827c-8a6fbd479082"
Context Example
{
"TaegisXDR": {
"CommentCreate": {
"id": "593fa115-abad-4a52-9fc4-2ec403a8a1e4"
}
}
}
taegis-create-investigation
Base Command
!taegis-create-investigation
Inputs
| Argument Name | Description | Required |
|---|---|---|
| description | The subject or description of the investigation | True |
| priority | The priority for the investigiation [Default: 3] | False |
Command Example
!taegis-create-investigation priority=1 description="XSOAR Created Investigation"
Context Example
{
"TaegisXDR": {
"Investigation": {
"id": "593fa115-abad-4a52-9fc4-2ec403a8a1e4"
}
}
}
taegis-execute-playbook
Base Command
!taegis-execute-playbook
Inputs
| Argument Name | Description | Required |
|---|---|---|
| id | Playbook instance ID to execute | True |
| inputs | JSON object of inputs to pass into the playbook execution | False |
Command Example
!taegis-execute-playbook id=UGxheWJvb2tJbnN0YW5jZTphZDNmNzBlZi1mN2U0LTQ0OWYtODJiMi1hYWQwMjQzZTA2NTg=
!taegis-execute-playbook id=UGxheWJvb2tJbnN0YW5jZTphZDNmNzBlZi1mN2U0LTQ0OWYtODJiMi1hYWQwMjQzZTA2NTg= inputs=`{'myvar': 'myval'}`
Context Example
{
"id": "UGxheWJvb2tFeGVjdXRpb246NGYwZDZiNGQtNWNiZS00NDkxLTg3YzYtMDZkNjkxYzMwMTg4"
}
taegis-fetch-alerts
Base Command
!taegis-fetch-alerts
Input
| Argument Name | Description | Default | Required |
|---|---|---|---|
| ids | A list of alerts by IDs | 936c1cc1-db8f-430c-837c-1c914fcca35a |
False |
| limit | Number of results to when ids is not defined |
10 |
False |
| offset | The result to start from when ids is not defined |
0 |
False |
| cql_query | The query to utilize when searching for Alerts | from alert severity >= 0.6 and status='OPEN' |
False |
Command Examples
!taegis-fetch-alerts ids=`["6594e97f-a898-5b28-82b2-ea03293cdaa1"]`
Context Example
{
"TaegisXDR": {
"Alerts": [
{
"id": "c4f33b53-eaba-47ac-8272-199af0f7935b",
"metadata": {
"title": "Test Alert",
"description": "This is a test alert",
"severity": 0.5,
}
}
]
}
}
taegis-fetch-assets
Base Command
!taegis-fetch-assets
Input
| Argument Name | Description | Default | Required |
|---|---|---|---|
| page | 0 |
False | |
| page_size | 10 |
False | |
| endpoint_type | False | ||
| host_id | ID of the asset to fetch | e43b545a-580a-4047-b489-4338c1cc4ba1 |
False |
| hostname | False | ||
| investigation_id | False | ||
| ip_address | False | ||
| mac_address | False | ||
| os_family | False | ||
| os_version | False | ||
| sensor_version | False | ||
| username | False |
Command Examples
!taegis-fetch-assets
!taegis-fetch-assets page=1 page_size=5
!taegis-fetch-assets hostname=MyHostname01
!taegis-fetch-assets host_id=e43b545a-580a-4047-b489-4338c1cc4ba1
Context Example
{
"TaegisXDR": {
"Assets": [
{
"id": "",
"ingestTime": "",
"createdAt": "",
"updatedAt": "",
"deletedAt": "",
"biosSerial": "",
"firstDiskSerial": "",
"systemVolumeSerial": "",
"sensorVersion": "",
"endpointPlatform": "",
"hostnames": [{"id": ", "hostname": ""],
"architecture": "",
"osFamily": "",
"osVersion": "",
"osDistributor": "",
"osRelease": "",
"systemType": "",
"osCodename": "",
"kernelRelease": "",
"kernelVersion": "",
"tags": [ "key": "", "tag": ""],
"endpointType": "",
"hostId": "",
"sensorId": "",
}
]
}
}
taegis-fetch-comment
Base Command
!taegis-fetch-comment
Inputs
| Argument Name | Description | Required |
|---|---|---|
| id | The ID of the comment to fetch | True |
Command Example
!taegis-fetch-comment id=ff9ca818-4749-4ccb-883a-2ccc6f6c9e0f
Context Example
{
"TaegisXDR": {
"Comment": {
"author_user": {
"email_normalized": "myuser@email.com",
"given_name": "John",
"family_name": "Smith",
"id": "auth0|000000000000000000000001",
},
"id": "ff9ca818-4749-4ccb-883a-2ccc6f6c9e0f",
"comment": "This is a comment in an investigation",
"created_at": "2022-01-01T13:04:57.17234Z",
"deleted_at": None,
"modified_at": None,
"parent_id": "c2e09554-833e-41a1-bc9d-8160aec0d70d",
"parent_type": "investigation",
}
}
}
taegis-fetch-comments
Base Command
!taegis-create-comments
Inputs
| Argument Name | Description | Required |
|---|---|---|
| parent_id | The investigation ID to fetch comments for | True |
Command Example
!taegis-fetch-comments parent_id=c2e09554-833e-41a1-bc9d-8160aec0d70d
Context Example
{
"TaegisXDR": {
"Comments": [
{
"author_user": {
"email_normalized": "myuser@email.com",
"given_name": "John",
"family_name": "Smith",
"id": "auth0|000000000000000000000001",
},
"id": "ff9ca818-4749-4ccb-883a-2ccc6f6c9e0f",
"comment": "This is a comment in an investigation",
"created_at": "2022-01-01T13:04:57.17234Z",
"deleted_at": None,
"modified_at": None,
"parent_id": "c2e09554-833e-41a1-bc9d-8160aec0d70d",
"parent_type": "investigation",
},
{
"author_user": {
"email_normalized": "myuser@email.com",
"given_name": "John",
"family_name": "Smith",
"id": "auth0|000000000000000000000001",
},
"id": "ff9ca818-4749-4ccb-883a-2ccc6f6c1234",
"comment": "This is another comment",
"created_at": "2022-01-02T13:04:57.17234Z",
"deleted_at": None,
"modified_at": None,
"parent_id": "c2e09554-833e-41a1-bc9d-8160aec0d70d",
"parent_type": "investigation",
}
]
}
}
taegis-fetch-endpoint
Base Command
!taegis-fetch-endpoint
Inputs
| Argument Name | Description | Required |
|---|---|---|
| id | Endpoint ID to fetch | True |
Command Example
!taegis-fetch-endpoint id=ff9ca818-4749-4ccb-883a-2ccc6f6c9e0f
Context Example
{
"TaegisXDR": {
"assetEndpointInfo": {
"hostId": "",
"hostName": "",
"actualIsolationStatus": "",
"allowedDomain": "",
"desiredIsolationStatus": "",
"firstConnectTime": "",
"moduleHealth": {
"enabled": ""
"lastRunningTime": "",
"moduleDisplayName": "",
}
"lastConnectAddress": "",
"lastConnectTime": "",
"sensorVersion": ""
}
}
}
taegis-fetch-investigation
Base Command
!taegis-fetch-investigation
Inputs
| Argument Name | Description | Required |
|---|---|---|
| id | Investigation ID to lookup | True |
Command Example
!taegis-fetch-investigation id=936c1cc1-db8f-430c-837c-1c914fcca35a
Context Example
{
"TaegisXDR": {
"Investigations": [
{
"archived_at": None,
"created_at": "2022-02-02T13:53:35Z",
"description": "Test Investigation",
"id": "c2e09554-833e-41a1-bc9d-8160aec0d70d",
"key_findings": "",
"priority": 2,
"service_desk_id": "",
"service_desk_type": "",
"status": "Open",
"alerts2": [],
"url": "https://ctpx.secureworks.com/investigations/c2e09554-833e-41a1-bc9d-8160aec0d70d",
}
]
}
}
taegis-fetch-investigation-alerts
Base Command
!taegis-fetch-investigation-alerts
Inputs
| Argument Name | Description | Required |
|---|---|---|
| id | Investigation ID to lookup | True |
Command Example
!taegis-fetch-investigation-alerts id=936c1cc1-db8f-430c-837c-1c914fcca35a
Context Example
{
"TaegisXDR": {
"InvestigationAlerts": [
{
"id": "c4f33b53-eaba-47ac-8272-199af0f7935b",
"description": "Test Alert",
"message": "This is a test alert",
"severity": 0.5,
}
]
}
}
taegis-fetch-investigations
Base Command
!taegis-fetch-investigations
Inputs
| Argument Name | Description | Required |
|---|---|---|
| page | False | |
| page_size | False |
Command Example
!taegis-fetch-investigations
Context Example
{
"TaegisXDR": {
"Investigations": [
{
"description": "Test Investigation",
"id": "c2e09554-833e-41a1-bc9d-8160aec0d70d",
"key_findings": "",
"priority": 2,
"service_desk_id": "",
"service_desk_type": "",
"status": "Open"
}
]
}
}
taegis-fetch-playbook-execution
Base Command
!taegis-fetch-playbook-execution
Inputs
| Argument Name | Description | Required |
|---|---|---|
| id | Playbook execution ID to fetch | True |
Command Example
!taegis-fetch-playbook-execution id=UGxheWJvb2tFeGVjdXRpb246NGYwZDZiNGQtNWNiZS00NDkxLTg3YzYtMDZkNjkxYzMwMTg4
Context Example
{
"TaegisXDR": {
"PlaybookExecution": {
"createdAt": "2022-01-01T13:51:24Z",
"executionTime": 1442,
"id": "UGxheWJvb2tFeGVjdXRpb246NGYwZDZiNGQtNWNiZS00NDkxLTg3YzYtMDZkNjkxYzMwMTg4",
"inputs": {
"alert": {
"message": "Test Alert",
}
},
"instance": {
"name": "Test Alert Instance",
"playbook": {
"name": "Taegis.PagerDutyAlertEvent"
}
},
"outputs": "d6b65662-c1da-4109-8553-c5664918c952",
"state": "Completed",
"updatedAt": "2022-01-01T13:51:31Z"
}
}
}
taegis-fetch-users
Base Command
!taegis-fetch-users
Inputs
| Argument Name | Description | Required |
|---|---|---|
| id | The id of the user, in auth0 format |
False |
| The email of the user | False | |
| status | The users to find based on status | False |
| page | False | |
| page_size | False |
Command Example
!taegis-fetch-users id="auth0|123456"
Context Example
{
"TaegisXDR": {
"Users": [
{
"email": "myuser@email.com",
"family_name": "Smith",
"given_name": "John",
"status": "Registered",
"user_id": "auth0|123456"
}
]
}
}
taegis-isolate-asset
Base Command
!taegis-isolate-asset
Input
| Argument Name | Description | Default | Required |
|---|---|---|---|
| id | ID of the asset to isolate | e43b545a-580a-4047-b489-4338c1cc4ba1 |
True |
| reason | The reason for the isolation | See ticket 12345 |
True |
Command Examples
!taegis-isolate-asset id="e43b545a-580a-4047-b489-4338c1cc4ba1" reason="See ticket 12345"
Context Example
{
"TaegisXDR": {
"AssetIsolation": {
"id": "e43b545a-580a-4047-b489-4338c1cc4ba1"
}
}
}
taegis-update-alert-status
Base Command
!taegis-update-alert-status
Input
| Argument Name | Description | Default | Required |
|---|---|---|---|
| ids | A comma-separated list of alerts by IDs | alert://priv:crowdstrike:11772:1666269058114:59284e28-4ec8-542b-a4a1-452c3688bc1a |
True |
| status | The status to update the alert(s) with | FALSE_POSITIVE |
True |
| reason | A comment/reason for the alert status update | See ticket 13245 |
False |
Permitted Status Values
- FALSE_POSITIVE
- NOT_ACTIONABLE
- OPEN
- TRUE_POSITIVE_BENIGN
- TRUE_POSITIVE_MALICIOUS
Command Examples
!taegis-update-alert-status ids="alert://priv:crowdstrike:11772:1677742145475:07e2d9cc-0a04-55ec-890a-97f39d63698e" status=NOT_ACTIONABLE reason="Test Reason"
Context Example
{
"TaegisXDR": {
"AlertStatusUpdate": {
"reason": "feedback updates successfully applied",
"resolution_status": "SUCCESS"
}
}
}
taegis-update-comment
Base Command
!taegis-update-comment
Inputs
| Argument Name | Description | Required |
|---|---|---|
| comment | The comment string to add to the investigation | True |
| id | The comment ID to update | True |
Command Example
!taegis-update-comment id="ff9ca818-4749-4ccb-883a-2ccc6f6c9e0f" comment="Newly updated comment"
Context Example
{
"TaegisXDR": {
"CommentUpdate": {
"id": "593fa115-abad-4a52-9fc4-2ec403a8a1e4"
}
}
}
taegis-update-investigation
Base Command
!taegis-update-investigation
Inputs
| Argument Name | Description | Required |
|---|---|---|
| id | Investigation ID to update | True |
| key_findings | False | |
| prioirity | The priority of the Investigation (1-5) | False |
| service_desk_id | An ID or ticket # to relate to an Investigation | False |
| service_desk_type | The type of id related to an investigation (e.g. Jira) | False |
| status | The current status of the Investigation | False |
| assignee_id | The id of a user to assign, in auth0|12345 format |
False |
Note: At least 1 of the above inputs (in addition to id) must be defined
Permitted Status Values
- Active
- Awaiting Action
- Closed: Authorized Activity
- Closed: Confirmed Security Incident
- Closed: False Positive Alert
- Closed: Inconclusive
- Closed: Informational
- Closed: Not Vulnerable
- Closed: Threat Mitigated
- Open
- Suspended
Command Example
!taegis-update-investigation id="936c1cc1-db8f-430c-837c-1c914fcca35a" priority=3 status="Open" service_desk_id="XDR-1234" service_desk_type="Jira"
Context Example
{
"TaegisXDR": {
"InvestigationUpdate": {
"id": "c2e09554-833e-41a1-bc9d-8160aec0d70d"
}
}
}
taegis-unarchive-investigation
Base Command
!taegis-unarchive-investigation
Inputs
| Argument Name | Description | Required |
|---|---|---|
| id | The investigation id to unarchive | True |
Command Example
!taegis-unarchive-investigation id=c207ca4c-8a78-4408-a056-49f05d6eb77d
Context Example
{
"TaegisXDR": {
"UnarchivedInvestigation": {
"id": "c207ca4c-8a78-4408-a056-49f05d6eb77d"
}
}
}
Configuration parameters
environment— Taegis Environment (required)insecure— Trust any certificate (not secure)proxy— Use system proxy settingsclient_id— Client ID (required)client_secret— Client Secret (required)isFetch— Fetch incidentsmax_fetch— Maximum number of incidents per fetchfirst_fetch— First fetch time intervalincidentFetchInterval— Incidents Fetch IntervalincidentType— Incident typeinclude_assets— Include Assets in Fetch
Commands (18)
-
taegis-archive-investigationArchive an investigation
-
taegis-create-commentCreate a comment on an investigation
-
taegis-create-investigationCreate an Investigation within Taegis
-
taegis-execute-playbookExecutes a Taegis playbook instance
-
taegis-fetch-alertsList Taegis alerts by ID
-
taegis-fetch-assetsFetch assets based on search criteria
-
taegis-fetch-commentFetch comment by comment ID
-
taegis-fetch-commentsFetch comments by Parent Type and ID
-
taegis-fetch-endpointFetch endpoint information
-
taegis-fetch-investigationFetch all investigations or a specific investigation
-
taegis-fetch-investigation-alertsFetch Alert IDs related to a specific investigation
-
taegis-fetch-playbook-executionFetch the results of a Taegis playbook instance execution
-
taegis-fetch-usersFetch a user by ID or email address
-
taegis-isolate-assetIsolate a specific asset
-
taegis-unarchive-investigationUnarchive an investigation
-
taegis-update-alert-statusUpdate the status of an alert
-
taegis-update-commentUpdate an existing comment
-
taegis-update-investigationUpdate an existing investigation
import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 from typing import Any, Dict """ CONSTANTS """ AUTH_ENDPOINT = "/auth/api/v2/auth/token" GRAPHQL_ENDPOINT = "/graphql" ENV_URLS = { "us1": {"api": "https://api.ctpx.secureworks.com", "xdr": "https://ctpx.secureworks.com"}, "us2": {"api": "https://api.delta.taegis.secureworks.com", "xdr": "https://delta.taegis.secureworks.com"}, "eu": {"api": "https://api.echo.taegis.secureworks.com", "xdr": "https://echo.taegis.secureworks.com"}, } ALERT_STATUSES = set(( "FALSE_POSITIVE", "NOT_ACTIONABLE", "OPEN", "TRUE_POSITIVE_BENIGN", "TRUE_POSITIVE_MALICIOUS", )) ASSET_SEARCH_FIELDS = (( "endpoint_type", "host_id", "hostname", "investigation_id", "ip_address", "mac_address", "os_family", "os_version", "sensor_version", "username", )) COMMENT_TYPES = set(( "investigation", )) INVESTIGATION_STATUSES = set(( "Open", "Active", "Awaiting Action", "Suspended", "Closed: Authorized Activity", "Closed: Confirmed Security Incident", "Closed: False Positive Alert", "Closed: Inconclusive", "Closed: Informational", "Closed: Not Vulnerable", "Closed: Threat Mitigated", )) INVESTIGATION_UPDATE_FIELDS = set(("key_findings", "priority", "status", "service_desk_id", "service_desk_type", "assignee_id")) """ CLIENT """ class Client(BaseClient): """ Secureworks Taegis XDR Client class for implementing API logic with Taegis """ _auth_header = {"access_token": "None"} def __init__( self, client_id: str, client_secret: str, base_url: str, proxy: bool = False, verify: bool = True, ) -> None: super().__init__(base_url=base_url, verify=verify, proxy=proxy) self.base_url = base_url self._client_id = client_id self._client_secret = client_secret self.verify = verify return def auth(self) -> None: """Authenticate to the Taegis API using client_id and client_secret See the documentation for obtaining the client ID and secret: https://docs.ctpx.secureworks.com/apis/api_authenticate/ """ response = self._http_request( "POST", AUTH_ENDPOINT, headers={"Content-Type": "application/json"}, json_data={"grant_type": "client_credentials"}, auth=(self._client_id, self._client_secret), ) token = response.get("access_token", None) self._auth_header = {"Authorization": f"Bearer {token}"} return def graphql_run(self, query: str, variables: Dict[str, Any] = None): """Perform a GraphQL query :type query: ``str`` :param query: The GraphQL query :type variables: ``Dict[str, Any]`` :param variables: The variables to utilize with the query """ json_data: Dict[str, Any] = {"query": query} if variables: json_data["variables"] = variables response = self._http_request( method="POST", url_suffix=GRAPHQL_ENDPOINT, json_data=json_data, headers=self._auth_header, ) return response def test(self) -> Dict[str, Any]: """ Get the current API/asset version for testing auth and connectivity """ response = self._http_request( method="GET", url_suffix="/assets/version", headers=self._auth_header, ) return response """ COMMANDS """ def create_comment_command(client: Client, env: str, args=None): if not args.get("comment"): raise ValueError("Cannot create comment, comment cannot be empty") if not args.get("parent_id"): raise ValueError("Cannot create comment, parent_id cannot be empty") parent_type = args.get("parent_type", "investigation").lower() if parent_type not in COMMENT_TYPES: raise ValueError( f"The provided comment parent type, {parent_type}, is not valid. " f"Supported Parent Types Values: {COMMENT_TYPES}" ) query = """ mutation createComment ($comment: CommentInput!) { createComment(comment: $comment) { id } } """ variables = { "comment": { "comment": args.get("comment"), "parent_id": args.get("parent_id"), "parent_type": parent_type, "section_id": args.get("section_id", ""), "section_type": args.get("section_type", ""), } } result = client.graphql_run(query=query, variables=variables) try: comment = result["data"]["createComment"] except (KeyError, TypeError): raise ValueError(f"Failed to create comment: {result['errors'][0]['message']}") results = CommandResults( outputs_prefix="TaegisXDR.CommentCreate", outputs_key_field="id", outputs=comment, readable_output=tableToMarkdown( "Taegis Comment", comment, removeNull=True, ), raw_response=result, ) return results def create_investigation_command(client: Client, env: str, args=None): query = """ mutation ($investigation: InvestigationInput!) { createInvestigation(investigation: $investigation) { id } } """ variables = { "investigation": { "description": args.get("description", "Demisto Created Investigation"), "priority": args.get("priority", 2), "status": "Open", } } result = client.graphql_run(query=query, variables=variables) try: investigation = result["data"]["createInvestigation"] investigation["url"] = generate_id_url(env, "investigations", investigation["id"]) except (KeyError, TypeError): raise ValueError(f"Failed to create investigation: {result['errors'][0]['message']}") results = CommandResults( outputs_prefix="TaegisXDR.Investigation", outputs_key_field="id", outputs=investigation, readable_output=tableToMarkdown( "Taegis Investigation", investigation, removeNull=True, url_keys=("url"), ), raw_response=result, ) return results def execute_playbook_command(client: Client, env: str, args=None): playbook_id = args.get("id") if not playbook_id: raise ValueError("Cannot execute playbook, missing playbook_id") query = """ mutation executePlaybookInstance( $playbookInstanceId: ID! $parameters: JSONObject ) { executePlaybookInstance( playbookInstanceId: $playbookInstanceId parameters: $parameters ) { id } } """ playbook_inputs = args.get("inputs", {}) variables = { "playbookInstanceId": playbook_id, "parameters": playbook_inputs, } result = client.graphql_run(query=query, variables=variables) if not result.get("data"): raise ValueError(f"Failed to execute playbook: {result['errors'][0]['message']}") execution = result["data"]["executePlaybookInstance"] execution["url"] = generate_id_url(env, "automations/playbook-executions", execution["id"]) results = CommandResults( outputs_prefix="TaegisXDR.Execution", outputs_key_field="id", outputs=execution, readable_output=tableToMarkdown( "Taegis Playbook Execution", execution, removeNull=True, url_keys=("url"), ), raw_response=result, ) return results def fetch_alerts_command(client: Client, env: str, args=None): """ Fetch a specific alert or a list of alerts based on a CQL Taegis query """ variables: dict = { "cql_query": args.get("cql_query", "from alert severity >= 0.6 and status='OPEN'"), "limit": args.get("limit", 10), "offset": args.get("offset", 0), "ids": args.get("ids", []), # ["alerts://id1", "alerts://id2"] } fields: str = """ status reason alerts { total_results list { id tenant_id status suppressed suppression_rules { id version } resolution_reason attack_technique_ids entities{ entities relationships{ from_entity relationship to_entity } } metadata { engine { name } creator { detector { version detector_id } rule { rule_id version } } title description confidence severity created_at { seconds } } investigation_ids { id } sensor_types } } """ if args.get("ids"): field = "alertsServiceRetrieveAlertsById" query = """ query alertsServiceRetrieveAlertsById($ids: [String!]) { alertsServiceRetrieveAlertsById( in: { iDs: $ids } ) { %s } } """ % (fields) if type(variables["ids"]) == str: variables["ids"] = variables["ids"].split(",") # alerts://id1,alerts://id2 variables["ids"] = [x.strip() for x in variables["ids"]] # Ensure no whitespace else: field = "alertsServiceSearch" query = """ query alertsServiceSearch($cql_query: String, $limit: Int, $offset: Int) { alertsServiceSearch( in: { cql_query:$cql_query, offset:$offset, limit:$limit } ) { %s } } """ % (fields) result = client.graphql_run(query=query, variables=variables) alerts = result["data"][field]["alerts"]["list"] for alert in alerts: alert.update({"url": generate_id_url(env, "alerts", alert["id"])}) results = CommandResults( outputs_prefix="TaegisXDR.Alerts", outputs_key_field="id", outputs=alerts, readable_output=tableToMarkdown( "Taegis Alerts", alerts, removeNull=True, url_keys=("url"), ), raw_response=result, ) return results def fetch_assets_command(client: Client, env: str, args=None): page = arg_to_number(args.get("page")) or 0 page_size = arg_to_number(args.get("page_size")) or 10 variables: Dict[str, Any] = { "input": {}, "pagination_input": { "limit": page_size, "offset": page_size * page, } } # Loop over allowed search fields and add valid search options to the query variables for field in ASSET_SEARCH_FIELDS: if args.get(field): variables["input"][field] = args.get(field).strip() query = """ query searchAssetsV2($input: SearchAssetsInput!, $pagination_input: SearchAssetsPaginationInput!) { searchAssetsV2(input: $input, paginationInput:$pagination_input) { assets { id ingestTime createdAt updatedAt deletedAt biosSerial firstDiskSerial systemVolumeSerial sensorVersion endpointPlatform architecture osFamily osVersion osDistributor osRelease systemType osCodename kernelRelease kernelVersion hostnames { id hostname }, tags { key tag } endpointType hostId sensorId } } } """ result = client.graphql_run(query=query, variables=variables) try: assets = result["data"]["searchAssetsV2"]["assets"] except (KeyError, TypeError): raise ValueError(f"Failed to fetch assets: {result['errors'][0]['message']}") results = CommandResults( outputs_prefix="TaegisXDR.Assets", outputs_key_field="id", outputs=assets, readable_output=tableToMarkdown( "Taegis Assets", assets, removeNull=True, ), raw_response=result, ) return results def fetch_comment_command(client: Client, env: str, args=None): comment_id = args.get("id") if not comment_id: raise ValueError("Cannot fetch comment, missing comment_id") query = """ query comment ($comment_id: ID!) { comment(comment_id: $comment_id) { author_user { id family_name given_name email_normalized } id comment modified_at deleted_at created_at parent_id parent_type } } """ variables = {"comment_id": comment_id} result = client.graphql_run(query=query, variables=variables) try: comment = result["data"]["comment"] except (KeyError, TypeError): raise ValueError("Could not locate comment by provided ID") results = CommandResults( outputs_prefix="TaegisXDR.Comment", outputs_key_field="id", outputs=comment, readable_output=tableToMarkdown( "Taegis Comment", comment, removeNull=True, ), raw_response=result, ) return results def fetch_comments_command(client: Client, env: str, args=None): if not args.get("parent_id"): raise ValueError("Cannot fetch comments, missing parent_id") parent_type = args.get("parent_type", "investigation") if parent_type not in COMMENT_TYPES: raise ValueError(( f"The provided comment parent type, {parent_type}, is not valid. " f"Supported Parent Types Values: {parent_type}" )) query = """ query commentsByParent ($parent_type: String!, $parent_id: String!) { commentsByParent(parent_type: $parent_type,parent_id:$parent_id) { author_user { id family_name given_name email_normalized } id comment modified_at deleted_at created_at parent_id parent_type } } """ variables = { "parent_id": args.get("parent_id"), "parent_type": parent_type } result = client.graphql_run(query=query, variables=variables) try: comments = result["data"]["commentsByParent"] except (KeyError, TypeError): raise ValueError(f"Failed to fetch comments: {result['errors'][0]['message']}") results = CommandResults( outputs_prefix="TaegisXDR.Comments", outputs_key_field="id", outputs=comments, readable_output=tableToMarkdown( "Taegis Comments", comments, removeNull=True, ), raw_response=result, ) return results def fetch_endpoint_command(client: Client, env: str, args=None): if not args.get("id"): raise ValueError("Cannot fetch endpoint information, missing id") variables: Dict[str, Any] = { "id": args.get("id") } query = """ query assetEndpointInfo($id: ID!) { assetEndpointInfo(id: $id) { hostId hostName actualIsolationStatus allowedDomain desiredIsolationStatus firstConnectTime moduleHealth { enabled lastRunningTime moduleDisplayName } lastConnectAddress lastConnectTime sensorVersion } } """ result = client.graphql_run(query=query, variables=variables) try: endpoint = result["data"]["assetEndpointInfo"] except (KeyError, TypeError): raise ValueError(f"Failed to fetch endpoint information: {result['errors'][0]['message']}") results = CommandResults( outputs_prefix="TaegisXDR.Endpoint", outputs_key_field="hostId", outputs=endpoint, readable_output=tableToMarkdown( "Taegis Endpoint", endpoint, removeNull=True, ), raw_response=result, ) return results def fetch_incidents(client: Client, max_fetch: int = 15, include_assets: bool = True): """ Fetch Taegis Investigations for the use with "Fetch Incidents" """ if not 0 < int(max_fetch) < 201: raise ValueError("Max Fetch must be between 1 and 200") asset_query = "" if include_assets: demisto.debug("include_assets=True, fetching assets with investigation") asset_query = "assets {id hostnames {id hostname} tags {tag}}" query = """ query investigations( $page: Int, $perPage: Int, $status: [String], $createdAfter: String, $orderByField: OrderFieldInput, $orderDirection: OrderDirectionInput ) { allInvestigations( page: $page, perPage: $perPage, status: $status, createdAfter: $createdAfter, orderByField: $orderByField, orderDirection: $orderDirection ) { id tenant_id description key_findings assignee { name id email } alerts2 { id suppressed status priority { value } metadata { title description created_at { seconds } severity confidence } } archived_at created_at updated_at service_desk_id service_desk_type latest_activity priority status %s } } """ % (asset_query) variables = { "orderByField": "created_at", "orderDirection": "asc", "page": 0, "perPage": max_fetch, "status": ["Open", "Active", "Awaiting Action"] } last_run = demisto.getLastRun() demisto.debug(f"Last Fetch Incident Run: {last_run}") now = datetime.now() start_time = str(now - timedelta(days=1)) # Default start if first ever run if last_run and "start_time" in last_run: start_time = last_run.get("start_time") variables["createdAfter"] = start_time result = client.graphql_run(query=query, variables=variables) if result.get("errors") and result["errors"]: raise DemistoException(f"Error when fetching investigations: {result['errors'][0]['message']}") incidents = [] for investigation in result["data"]["allInvestigations"]: # createdAfter really means createdAtOrAfter so skip the duplicate if start_time == investigation["created_at"]: continue # Skip archived, if necessary if investigation["archived_at"]: demisto.debug(f"Skipping Archived Investigation: {investigation['description']} ({investigation['id']})") continue demisto.debug(f"Found New Investigation: {investigation['description']} ({investigation['id']})") incidents.append({ "name": investigation["description"], "occured": investigation["created_at"], "rawJSON": json.dumps(investigation) }) demisto.debug(f"Located {len(incidents)} Incidents") last_run = str(now) if not incidents else incidents[-1]["occured"] demisto.debug(f"Last Run/Incident Time: {last_run}") demisto.setLastRun({"start_time": last_run}) demisto.incidents(incidents) return incidents def fetch_investigation_alerts_command(client: Client, env: str, args=None): investigation_id = args.get("id") page = args.get("page", 0) page_size = args.get("page_size", 10) if not investigation_id: raise ValueError("Cannot fetch investigation, missing investigation_id") query = """ query investigationAlerts($investigation_id: ID!, $page: Int, $perPage: Int) { investigationAlerts(investigation_id: $investigation_id, page: $page, perPage: $perPage) { alerts { id } alerts2 { id } totalCount } } """ variables = {"page": page, "perPage": page_size, "investigation_id": investigation_id} result = client.graphql_run(query=query, variables=variables) try: alerts = result["data"]["investigationAlerts"]["alerts"] except (KeyError, TypeError): alerts = [] for alert in alerts: alert.update({"url": generate_id_url(env, "alerts", alert["id"])}) results = CommandResults( outputs_prefix="TaegisXDR.InvestigationAlerts", outputs_key_field="id", outputs=alerts, readable_output=tableToMarkdown( "Taegis Investigation Alerts", alerts, removeNull=True, url_keys=("url"), ), raw_response=result, ) return results def fetch_investigation_command(client: Client, env: str, args=None): investigation_id = args.get("id", None) page = args.get("page", 0) page_size = args.get("page_size", 10) status = args.get("status", []) fields = """ id tenant_id description key_findings alerts2 { id suppressed status priority { value } metadata { title description created_at { seconds } severity confidence } } genesis_alerts2 { id suppressed status priority { value } metadata { title description created_at { seconds } severity confidence } } assignee { name id email } archived_at created_at updated_at service_desk_id service_desk_type latest_activity priority status assets { id hostnames { id hostname } tags { tag } } """ if investigation_id: query = """ query investigation($investigation_id: ID!) { investigation(investigation_id: $investigation_id) { %s } } """ % (fields) variables = {"investigation_id": investigation_id} result = client.graphql_run(query=query, variables=variables) else: query = """ query investigations($page: Int, $perPage: Int, $status: [String]) { allInvestigations(page: $page, perPage: $perPage, status: $status) { %s } } """ % (fields) variables = {"page": page, "perPage": page_size, "status": status} result = client.graphql_run(query=query, variables=variables) try: investigations = [result["data"]["investigation"]] if investigation_id else result["data"]["allInvestigations"] except (KeyError, TypeError): investigations = [] for investigation in investigations: investigation.update({"url": generate_id_url(env, "investigations", investigation["id"])}) results = CommandResults( outputs_prefix="TaegisXDR.Investigations", outputs_key_field="id", outputs=investigations, readable_output=tableToMarkdown( "Taegis Investigations", investigations, removeNull=True, url_keys=("url"), ), raw_response=result, ) return results def fetch_playbook_execution_command(client: Client, env: str, args=None): execution_id = args.get("id") if not execution_id: raise ValueError("Cannot fetch playbook execution, missing execution id") query = """ query playbookExecution($playbookExecutionId: ID!) { playbookExecution(playbookExecutionId: $playbookExecutionId) { id state instance { name playbook { name } } inputs createdAt updatedAt executionTime outputs } } """ variables = { "playbookExecutionId": execution_id } result = client.graphql_run(query=query, variables=variables) try: execution = result['data']["playbookExecution"] execution["url"] = generate_id_url(env, "automations/playbook-executions", execution["id"]) except (KeyError, TypeError): raise ValueError(f"Failed to fetch playbook execution: {result['errors'][0]['message']}") results = CommandResults( outputs_prefix="TaegisXDR.PlaybookExecution", outputs_key_field="id", outputs=execution, readable_output=tableToMarkdown( "Taegis Playbook Execution", execution, removeNull=True, url_keys=("url"), ), raw_response=result, ) return results def fetch_users_command(client: Client, env: str, args=None): page = int(args.get("page", 0)) page_size = int(args.get("page_size", 10)) variables: Dict[str, Any] = { "filters": { "status": args.get("status", ""), "perPage": page_size, "pageOffset": page_size * page, } } fields = "user_id email family_name given_name status" if args.get("id"): if not args["id"].startswith("auth0"): raise ValueError("id MUST be in 'auth0|12345' format") query = """ query ($ids: [String!]) { tdrusersByIDs (userIDs: $ids) { %s } } """ % (fields) variables = {"ids": [args["id"]]} else: query = """ query ($filters: TDRUsersSearchInput) { tdrUsersSearch (filters: $filters) { results { %s } } } """ % (fields) if args.get("email"): variables["filters"]["emails"] = args["email"] result = client.graphql_run(query=query, variables=variables) try: if args.get("id"): user = result["data"]["tdrusersByIDs"] else: user = result["data"]["tdrUsersSearch"]["results"] except (KeyError, TypeError): raise ValueError(f"Failed to fetch user information: {result['errors'][0]['message']}") results = CommandResults( outputs_prefix="TaegisXDR.Users", outputs_key_field="user_id", outputs=user, readable_output=tableToMarkdown( "Taegis Users", user, removeNull=True, ), raw_response=result, ) return results def isolate_asset_command(client: Client, env: str, args=None): if not args.get("id"): raise ValueError("Cannot isolate asset, missing id") if not args.get("reason"): raise ValueError("Cannot isolate asset, missing reason") variables: Dict[str, Any] = { "id": args.get("id"), "reason": args.get("reason") } query = """ mutation isolateAsset ($id: ID!, $reason: String!) { isolateAsset (id: $id, reason: $reason) { id } } """ result = client.graphql_run(query=query, variables=variables) try: isolation = result["data"]["isolateAsset"] except (KeyError, TypeError): raise ValueError(f"Failed to isolate asset: {result['errors'][0]['message']}") results = CommandResults( outputs_prefix="TaegisXDR.AssetIsolation", outputs_key_field="id", outputs=isolation, readable_output=tableToMarkdown( "Taegis Asset Isolation", isolation, removeNull=True, ), raw_response=result, ) return results def update_alert_status_command(client: Client, env: str, args=None): if not args.get("ids"): raise ValueError("Alert IDs must be defined") if not args.get("status"): raise ValueError("Alert status must be defined") if args.get("status").upper() not in ALERT_STATUSES: raise ValueError(( f"The provided status, {args['status']}, is not valid for updating an alert. " f"Supported Status Values: {ALERT_STATUSES}")) variables = { "alert_ids": argToList(args.get("ids")), "reason": args.get("reason", ""), "resolution_status": args.get("status"), } query = """ mutation alertsServiceUpdateResolutionInfo($alert_ids: [String!], $reason: String, $resolution_status: ResolutionStatus) { alertsServiceUpdateResolutionInfo( in: { alert_ids: $alert_ids, reason: $reason, resolution_status: $resolution_status } ) { resolution_status reason } } """ result = client.graphql_run(query=query, variables=variables) try: update_result = result["data"]["alertsServiceUpdateResolutionInfo"] except (KeyError, TypeError): raise ValueError(f"Failed to locate/update alert: {result['errors'][0]['message']}") results = CommandResults( outputs_prefix="TaegisXDR.AlertStatusUpdate", outputs_key_field="status", outputs=update_result, readable_output=tableToMarkdown( "Taegis Alert Update", update_result, removeNull=True, ), raw_response=result, ) return results def update_comment_command(client: Client, env: str, args=None): if not args.get("id"): raise ValueError("Cannot update comment, comment id cannot be empty") if not args.get("comment"): raise ValueError("Cannot update comment, comment cannot be empty") query = """ mutation updateComment ($comment_id: ID!, $comment: CommentUpdate!) { updateComment(comment_id: $comment_id, comment: $comment) { id } } """ variables = { "comment_id": args.get("id"), "comment": { "comment": args.get("comment") }, } result = client.graphql_run(query=query, variables=variables) try: comment = result["data"]["updateComment"] except (KeyError, TypeError): raise ValueError(f"Failed to locate/update comment: {result['errors'][0]['message']}") results = CommandResults( outputs_prefix="TaegisXDR.CommentUpdate", outputs_key_field="id", outputs=comment, readable_output=tableToMarkdown( "Taegis Comment", comment, removeNull=True, ), raw_response=result, ) return results def update_investigation_command(client: Client, env: str, args=None): investigation_id = args.get("id") if not investigation_id: raise ValueError("Cannot fetch investigation without investigation_id defined") if args.get("assignee_id"): if not args["assignee_id"].startswith("auth0") and args["assignee_id"] != "@secureworks": raise ValueError("assignee_id MUST be in 'auth0|12345' format or '@secureworks'") query = """ mutation ($investigation_id: ID!, $investigation: UpdateInvestigationInput!) { updateInvestigation(investigation_id: $investigation_id, investigation: $investigation) { id } } """ variables = {"investigation_id": investigation_id, "investigation": dict()} for field in INVESTIGATION_UPDATE_FIELDS: if not args.get(field): continue if field == "status" and args.get("status") not in INVESTIGATION_STATUSES: raise ValueError(( f"The provided status, {args['status']}, is not valid for updating an investigation. " f"Supported Status Values: {INVESTIGATION_STATUSES}")) variables["investigation"][field] = args.get(field) if not variables["investigation"]: raise ValueError(f"No valid investigation fields provided. Supported Update Fields: {INVESTIGATION_UPDATE_FIELDS}") result = client.graphql_run(query=query, variables=variables) try: investigation = result["data"]["updateInvestigation"] investigation["url"] = generate_id_url(env, "investigations", investigation["id"]) except (KeyError, TypeError): raise ValueError(f"Failed to locate/update investigation: {result['errors'][0]['message']}") results = CommandResults( outputs_prefix="TaegisXDR.InvestigationUpdate", outputs_key_field="id", outputs=investigation, readable_output=tableToMarkdown( "Taegis Investigation", investigation, removeNull=True, url_keys=("url"), ), raw_response=result, ) return results def archive_investigation_command(client: Client, env: str, args=None): investigation_id = args.get("id") if not investigation_id: raise ValueError("Cannot archive investigation, missing investigation id") query = """ mutation ($investigation_id: ID!) { archiveInvestigation(investigation_id: $investigation_id) { id } } """ variables = {"investigation_id": investigation_id} result = client.graphql_run(query=query, variables=variables) try: investigation = result["data"]["archiveInvestigation"] status = "Successfully Archived Investigation" except (KeyError, TypeError): raise ValueError(f"Could not locate investigation with id: {investigation_id}") archive_results = { "id": investigation_id, "result": investigation, "status": status, "url": generate_id_url(env, "investigations", investigation_id), } results = CommandResults( outputs_prefix="TaegisXDR.ArchivedInvestigation", outputs_key_field="id", outputs=archive_results, readable_output=tableToMarkdown( "Taegis Investigation Archiving", archive_results, removeNull=True, url_keys=("url"), ), raw_response=result, ) return results def unarchive_investigation_command(client: Client, env: str, args=None): investigation_id = args.get("id") if not investigation_id: raise ValueError("Cannot unarchive investigation, missing investigation id") query = """ mutation ($investigation_id: ID!) { unArchiveInvestigation(investigation_id: $investigation_id) { id } } """ variables = {"investigation_id": investigation_id} result = client.graphql_run(query=query, variables=variables) try: investigation = result["data"]["unArchiveInvestigation"] status = "Successfully Unarchived Investigation" except (KeyError, TypeError): if result["errors"][0].get("message"): investigation = {} status = "Investigation is not currently archived" else: raise ValueError(f"Could not locate investigation with id: {investigation_id}") archive_results = { "id": investigation_id, "result": investigation, "status": status, "url": generate_id_url(env, "investigations", investigation_id), } results = CommandResults( outputs_prefix="TaegisXDR.UnarchivedInvestigation", outputs_key_field="id", outputs=archive_results, readable_output=tableToMarkdown( "Taegis Investigation Unarchiving", archive_results, removeNull=True, url_keys=("url"), ), raw_response=result, ) return results def test_module(client: Client) -> str: """ Returns success if authentication was successful """ try: client.test() return "ok" except DemistoException as exception: raise DemistoException(exception) """ UTILITIES """ def generate_id_url(env: str, endpoint: str, element_id: str): element_id: str = element_id.replace('/', '%2F') return f"{ENV_URLS[env]['xdr']}/{endpoint}/{element_id}" """ MAIN """ def main(): command = demisto.command() demisto.info(f'Command being called is {command}') commands: Dict[str, Any] = { "fetch-incidents": fetch_incidents, "taegis-create-comment": create_comment_command, "taegis-create-investigation": create_investigation_command, "taegis-execute-playbook": execute_playbook_command, "taegis-fetch-alerts": fetch_alerts_command, "taegis-fetch-assets": fetch_assets_command, "taegis-fetch-comment": fetch_comment_command, "taegis-fetch-comments": fetch_comments_command, "taegis-fetch-endpoint": fetch_endpoint_command, "taegis-fetch-investigation": fetch_investigation_command, "taegis-fetch-investigation-alerts": fetch_investigation_alerts_command, "taegis-fetch-playbook-execution": fetch_playbook_execution_command, "taegis-fetch-users": fetch_users_command, "taegis-isolate-asset": isolate_asset_command, "taegis-update-alert-status": update_alert_status_command, "taegis-update-comment": update_comment_command, "taegis-update-investigation": update_investigation_command, "taegis-archive-investigation": archive_investigation_command, "taegis-unarchive-investigation": unarchive_investigation_command, "test-module": test_module, } PARAMS = demisto.params() try: if command not in commands: raise NotImplementedError(f'The "{command}" command has not been implemented.') environment = PARAMS.get("environment", "us1").lower() if not ENV_URLS.get(environment): raise ValueError(f"Unknown Environment Provided: {environment}") verify_cert = not PARAMS.get("insecure", False) client = Client( client_id=PARAMS.get("client_id"), client_secret=PARAMS.get("client_secret"), base_url=ENV_URLS[environment]["api"], proxy=PARAMS.get("proxy", False), verify=verify_cert, ) client.auth() if command == "test-module": result = commands[command](client=client) return_results(result) elif command == "fetch-incidents": commands[command](client=client, max_fetch=PARAMS.get("max_fetch"), include_assets=PARAMS.get("include_assets")) else: return_results(commands[command](client=client, env=environment, args=demisto.args())) except Exception as e: error_string = str(e) demisto.error(f"Error running command: {e}") if "Unauthorized" in error_string: error_string = "Invalid credentials (Client ID or Client Secret)" return_error(f"Failed to execute {command} command. Error: {error_string}") """ ENTRY POINT """ if __name__ in ("__main__", "__builtin__", "builtins"): main()