• Guides
    • Getting Started
            • Obtaining an Access Token
            • Testing Access
  • Use Cases
    • Asset Tagging
    • Assets in Investigations
    • Email High & Critical Alerts
    • Events API
    • Find assets by tag
    • Jupyter Notebooks & Taegis APIs
    • Webhook Receiver (Javascript)
  • API Reference
    • Assets
    • Audits
    • Clients
    • Connectors
    • Events
          • Query
          • Mutation
          • Objects
            • Aggregate
            • Aggregation
            • By
            • Event
            • EventQuery
            • EventQueryProgress
            • EventQueryResult
            • EventQueryResults
            • EventUser
            • ParseError
            • PipelineFunc
            • SortField
            • Subscription
            • ValidatorResults
          • Inputs
            • EventQueryOptions
          • Scalars
            • Boolean
            • ID
            • Int
            • JSONObject
            • String
            • Time
          • Interfaces
            • Node
    • Investigations
    • Playbooks
    • Threat Intel

Use Cases

Here you will find examples of common tasks using the APIs in various languages.

Asset Tagging

By defining the CIDR range you need to tag on line 23 and a tag to apply on line 24, this script will go through all of your organizations assets and tag them.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
from logging import exception
from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session
from ipaddress import ip_network, ip_address
from graphqlclient import GraphQLClient


import os
import json

client_id=os.environ.get('CLIENT_ID')
client_secret=os.environ.get('CLIENT_SECRET')

client = BackendApplicationClient(client_id=client_id)
oauth_client = OAuth2Session(client=client)
token = oauth_client.fetch_token(token_url='https://api.ctpx.secureworks.com/auth/api/v2/auth/token', 
								 client_id=client_id,
								 client_secret=client_secret)

gql_client = GraphQLClient('https://api.ctpx.secureworks.com/graphql')
gql_client.inject_token("Bearer " + token['access_token'], "Authorization")

# provide valid CIDR Range and tag string that needs to be added for the assets
cidr_range="" # Example 192.168.38.209/32
tag = ""

if not cidr_range or not tag:
	print("provide valid CIDR Range and tag string that needs to be added for the assets")
	quit()

class AssetsService:
	def __init__(self, gql_client):
		self.gql_client = gql_client

	def getAssetsInCidrRange(self, assets, net):
		results = []
		for asset in assets:
			for ip in asset["ipAddresses"]:
				try:
					if(ip_address(ip["ip"]) in net):
						results.append(asset)
				except Exception as e:
					print(e)
		return results

	def getAssetsHostIds(self, assets):
		results = []
		for asset in assets:
			results.append(asset["hostId"])
		return results

	# Add asset tag for the given hostId
	def createAssetTag(self, hostId, tag):
		query = """
            mutation {{
            createAssetTag(hostId:"{hostId}", tag:"{tag}") {{
                            id
                }}
            }}
            """.format(hostId=hostId, tag=tag)
		print(query)
		result = self.gql_client.execute(query)

		print(result)
		if not "errors" in result:
			print("Successfully added tag for the hostId ", hostId)

	# Get assets id, ip addresses and host ids for All assets - options are All | Active | Deleted
	def getAssets(self, filter_asset_state):
		query = """
           {{
                allAssets(
                    offset: 0,
                    limit: 100000,
                    filter_asset_state: {filter_asset_state}
                )
                {{
                    totalResults
                    assets {{
                        id
                        hostId
                        ipAddresses {{
                            ip
                        }}
                    }}
                }}
                }}
            """.format(filter_asset_state=filter_asset_state)
		result = self.gql_client.execute(query)
		parsedResults = json.loads(result)
		assetsList = parsedResults["data"]["allAssets"]["assets"]
		return assetsList


assetsService = AssetsService(gql_client)
allAssets = assetsService.getAssets('All') #options are All | Active | Deleted
# print("allAssets ",allAssets)

cidrRangeAssets = assetsService.getAssetsInCidrRange(allAssets, ip_network(cidr_range))
# print("cidrRangeAssets ",cidrRangeAssets)
cidrRangeAssetHostIds = assetsService.getAssetsHostIds(cidrRangeAssets)
# print("cidrRangeAssetHostIds ",cidrRangeAssetHostIds)

for hostId in cidrRangeAssetHostIds:
	try:
		assetsService.createAssetTag(hostId, tag)
	except Exception as e:
		print("Failed to add tag for the hostId {0} due to {1}".format(hostId, e))

Assets in Investigations

Given a time range (defined on line 21 and 22), find all assets that were involved in an investigation during that period.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import json
from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session
from graphqlclient import GraphQLClient

import os

client_id=os.environ.get('CLIENT_ID')
client_secret=os.environ.get('CLIENT_SECRET')
client = BackendApplicationClient(client_id=client_id)
oauth_client = OAuth2Session(client=client)

token = oauth_client.fetch_token(token_url='https://api.ctpx.secureworks.com/auth/api/v2/auth/token', 
								 client_id=client_id,
								 client_secret=client_secret)

gql_client = GraphQLClient('https://api.ctpx.secureworks.com/graphql')
gql_client.inject_token("Bearer " + token['access_token'], "Authorization")

# user inputs
investigations_created_before = ""
investigations_created_after = ""

if not investigations_created_before or not investigations_created_after:
    print("please provide valid before and after timelines to search for investigation assets (Ex: 2021-01-01T22:04:02Z)")
    quit()

query = '''
    {{
        investigationsSearch(query: "created_at >'{created_after}' and created_at <'{created_before}'")
            {{
                totalCount
                investigations {{
                   id
                   assets {{
                        id
                        hostId
                        tags {{
                            id
                            hostId
                            tenantId
                            createdAt
                            updatedAt
                            tag
                        }}
                   }}
                }} 
            }}
    }}
'''.format(created_after=investigations_created_after, created_before=investigations_created_before)
result = gql_client.execute(query)
parsed = json.loads(result)

print(json.dumps(parsed, indent=4))

Email High & Critical Alerts

Given a look back period (line 46), find all alerts that were above the .8 severity and send an email with their name, resolution, type, timestamp and description.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
from datetime import datetime, timedelta, timezone
from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session
from email.message import EmailMessage
import os
import json
import smtplib
# import pprint

client_id = os.environ.get('CLIENT_ID')
client_secret = os.environ.get('CLIENT_SECRET')
client = BackendApplicationClient(client_id=client_id)
oauth_client = OAuth2Session(client=client)
token = oauth_client.fetch_token(token_url='https://api.ctpx.secureworks.com/auth/api/v2/auth/token',
                                 client_id=client_id,
                                 client_secret=client_secret)


# IF you want to add more fields here to use later; see docs schema: https://docs.ctpx.secureworks.com/apis/alerts_api/
query = """query alerts($before: Time!, $after: Time!, $pageSize: Int!, $cursor: String!) {
    alertsByDate(after:$after, before:$before, orderByField: severity, orderDirection: desc, page_size: $pageSize, cursor: $cursor) {
        cursor
        edges {
          id
          alert_type
          confidence
          severity
          creator
          creator_version
          tenant_id
          message
          attack_categories
          related_entities
          description
          insert_timestamp{
            seconds
            nanos
          }
        }
    }
}"""

format = ("%Y-%m-%dT%H:%M:%SZ")  # "2020-01-22T20:04:02Z"
before = datetime.today().strftime(format)
# Depending on how far back in time you want to go, change days=1 to days=7 for a week for example
after = (datetime.today() - timedelta(days=1)).strftime(format)

cursor = ""
alerts_to_email = []
while True:
    print("Before: {}, After: {}".format(before, after))
    r = oauth_client.post('https://api.ctpx.secureworks.com/graphql', json={
        "query": query,
        "variables": {
            "pageSize": "1000",
            "after": after,
            "before": before,
            "cursor": cursor
        }
    })

    data = json.loads(r.content)['data']

    if data['alertsByDate'] is None or data['alertsByDate']['edges'] is None:
        break

    # print(len(data['alertsByDate']['edges']))
    # print(data['alertsByDate']['cursor'])
    print("-> next page")
    for alert in data['alertsByDate']['edges']:
        event_timestamp = 0
        alert_type = alert['creator']

        if alert['severity'] is not None and alert['severity'] > 0.8:
            # alert severity, alert name, alert type, alert date and time, and alert description, and if it’s there alert resolution.
            alerted_at = datetime.fromtimestamp(alert['insert_timestamp']['seconds'])
            alerts_to_email.append("""
name: {alert_name}
resolution: {resolution}
type: {alert_type}
timestamp: {timestamp}
description: {description}
            """.format(alert_name=alert['message'], alert_type=alert['creator'], timestamp=alerted_at,
                       description=alert['description'], resolution='TBD'))


    cursor = data['alertsByDate']['cursor']

email_body = "\n-----------------------------\n".join(alerts_to_email)
print(email_body)

# Send Email
msg = EmailMessage()
msg['From'] = "from-email"
msg['To'] = "to-email"
msg.set_content(email_body)
# hostname, smtp port
s = smtplib.SMTP('localhost', 1025)
# If you need to auth
# server.starttls()
# server.login(username, password)
s.send_message(msg)
s.quit()


Events API

This is a example for building a connector to the Taegis Events API service. Actual implementation will very upon your events use case.

Modify the QUERY_STR as you would in the Taegis Advanced Search box. This implementation will run until the maxRows is reached or the amount of events, whichever is lower.

Modify your result sets:

PAGE_SIZE max is 1000

MAX_ROWS max is 100000

Setup:

pip install gql oauthlib requests_oauthlib
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
import asyncio
import os

from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session

from gql import Client, gql
from gql.transport.websockets import WebsocketsTransport

CLIENT_ID = os.getenv("CLIENT_ID")
CLIENT_SECRET = os.getenv("CLIENT_SECRET")

TENANT_ID = os.getenv("TENANT_ID")
PAGE_SIZE = 10
MAX_ROWS = 50

QUERY_STR = "FROM process, persistence EARLIEST=-1d"

async def query(query_str: str, tenant_id: str, access_token: str):
    transport = WebsocketsTransport(
        url="wss://api.ctpx.secureworks.com/events/query",
        connect_args={
            "subprotocols": [
                "graphql-ws",
                f"x-tenant-context-{tenant_id}",
                f"access-token-{access_token}",
            ]
        },
    )

    async with Client(
        transport=transport,
        fetch_schema_from_transport=True,
    ) as session:

        subscription = gql(
            """subscription onEventQuery(
                            $query: String!,
                            $metadata: JSONObject,
                            $options: EventQueryOptions
                        ) {
                    eventQuery(query: $query, metadata: $metadata, options: $options) {
                        ...eventQueryResults
                        __typename
                    }
                }

                fragment eventQueryResults on EventQueryResults {
                    query {
                        id
                        query
                        status
                        reasons {
                            id
                            reason
                            __typename
                        }
                        submitted
                        completed
                        expires
                        types
                        metadata
                        __typename
                    }
                    result {
                        id
                        type
                        backend
                        status
                        reason
                        submitted
                        completed
                        expires
                        facets
                        rows
                        progress {
                            totalRows
                            totalRowsIsLowerBound
                            resultsTruncated
                            __typename
                        }
                        __typename
                    }
                    next
                    prev
                    __typename
                }"""
        )

        params = {
            "query": query_str,
            "options": {
                "timestampAscending": True,
                "pageSize": PAGE_SIZE,
                "maxRows": MAX_ROWS,
                "skipCache": True,
            },
        }

        results = []
        async for result in session.subscribe(subscription, variable_values=params):
            results.append(result)

    return results[:-1]


async def query_paginate(next_page: str, tenant_id: str, access_token: str):
    transport = WebsocketsTransport(
        url="wss://api.ctpx.secureworks.com/events/query",
        connect_args={
            "subprotocols": [
                "graphql-ws",
                f"x-tenant-context-{tenant_id}",
                f"access-token-{access_token}",
            ]
        },
    )

    async with Client(
        transport=transport,
        fetch_schema_from_transport=True,
    ) as session:

        subscription = gql(
            """subscription onEventPage($id: ID!) {
                eventPage(id: $id) {
                    ...eventQueryResults
                    __typename
                }
            }

            fragment eventQueryResults on EventQueryResults {
                query {
                    id
                    query
                    status
                    reasons {
                        id
                        reason
                        __typename
                    }
                    submitted
                    completed
                    expires
                    types
                    metadata
                    __typename
                }
                result {
                    id
                    type
                    backend
                    status
                    reason
                    submitted
                    completed
                    expires
                    facets
                    rows
                    progress {
                        totalRows
                        totalRowsIsLowerBound
                        resultsTruncated
                        __typename
                    }
                    __typename
                }
                next
                prev
                __typename
            }"""
        )

        params = {
            "id": next_page,
        }

        results = []
        async for result in session.subscribe(subscription, variable_values=params):
            results.append(result)

    return results[:-1]


if __name__ == "__main__":
    client = BackendApplicationClient(client_id=CLIENT_ID)
    oauth_client = OAuth2Session(client=client)
    token = oauth_client.fetch_token(
        token_url="https://api.ctpx.secureworks.com/auth/api/v2/auth/token",
        client_id=CLIENT_ID,
        client_secret=CLIENT_SECRET,
    )

    # for example, remove in implementation
    page = 1
    print("Page:", page)

    results = asyncio.run(
        query(
            query_str=QUERY_STR,
            tenant_id=TENANT_ID,
            access_token=token.get("access_token"),
        )
    )

    # for example
    print("# of result sets:", len(results))

    # For the example, we will place events in a schema keyed dictionary with the
    # data rows as list elements.
    #
    # Ex:
    #   events["process"] = [{"row1", "row2"}]
    #   events["auth] = [{"row1", "row2"}]
    events = {}
    for result in results:
        event_type = result.get("eventQuery", {}).get("result", {}).get("type", "error")
        
        # for example
        print(
            event_type,
            len(result.get("eventQuery", {}).get("result", {}).get("rows", [])),
        )
        
        events[event_type] = (
            result.get("eventQuery", {}).get("result", {}).get("rows", [])
        )

    # The next page reference can be in any or all of the returned result objects
    # There will be at most a single next page reference that we are extracting using set deduplication
    # Schemas that max out their results before another schema will not return a next reference
    next_page = list(
        {
            result.get("eventQuery", {}).get("next")
            for result in results
            if result.get("eventQuery", {}).get("next")
        }
    )
    while next_page:
        # for example
        page += 1
        print("Page:", page)
        print("Page Id:", next_page[0])
        
        results = asyncio.run(
            query_paginate(
                next_page=next_page[0],
                tenant_id=TENANT_ID,
                access_token=token.get("access_token"),
            )
        )

        print(len(results))

        for result in results:
            event_type = (
                result.get("eventPage", {}).get("result", {}).get("type", "error")
            )

            # for example
            print(
                event_type,
                len(result.get("eventPage", {}).get("result", {}).get("rows", [])),
            )

            events[event_type].extend(
                result.get("eventPage", {}).get("result", {}).get("rows", [])
            )

        # The next page reference can be in any or all of the returned result objects
        # There will be at most a single next page reference that we are extracting using set deduplication
        # Schemas that max out their results before another schema will not return a next reference
        next_page = list(
            {
                result.get("eventPage", {}).get("next")
                for result in results
                if result.get("eventPage", {}).get("next")
            }
        )

	# for example
    print()
    print("Result totals:")
    for key, results in events.items():
        print(key, len(results))

Example Output:

Page: 1
# of result sets: 2
persistence 6
process 10
Page: 2
Page Id: <query id>:1
# of result sets: 1
process 10
Page: 3
Page Id: <query id>:2
# of result sets: 1
process 10
Page: 4
Page Id: <query id>:3
# of result sets: 1
process 10
Page: 5
Page Id: <query id>:4
# of result sets: 1
process 9

Result totals:
persistence 6
process 49

Find assets by tag

If you want to find all assets that have been tagged, here is an example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session
from graphqlclient import GraphQLClient

import os
import json

client_id=os.environ.get('CLIENT_ID')
client_secret=os.environ.get('CLIENT_SECRET')

client = BackendApplicationClient(client_id=client_id)
oauth_client = OAuth2Session(client=client)
token = oauth_client.fetch_token(token_url='https://api.ctpx.secureworks.com/auth/api/v2/auth/token', client_id=client_id,
                                 client_secret=client_secret)
gql_client = GraphQLClient('https://api.ctpx.secureworks.com/graphql')
gql_client.inject_token("Bearer " + token['access_token'], "Authorization")

# provide tag to filter assets
tag = ""

if not tag:
    print("please provide a tag to filter on assets")
    quit()

query = '''
    {{ searchAssetsV2(input: {{ tag: "{tag}", filter_asset_state: All }}, paginationInput: {{ limit: 1000 }} ){{
                totalResults
                offset
                limit
                assets {{
                    id
                    hostId
                    rn
                    tenantId
                    sensorTenant
                    sensorId
                    hostnames {{
                      id
                      hostname
                    }}
                    tags {{
                      id
                      hostId
                      tenantId
                      createdAt
                      updatedAt
                      tag
                    }}
                   
                   }}
                }} 
            }}
'''.format(tag=tag)

print(query)

result = gql_client.execute(query)
parsed = json.loads(result)

print(json.dumps(parsed, indent=4))

Jupyter Notebooks & Taegis APIs

This is an example notebook utilizing the events API and Jupyter notebooks to pull data into pandas for manipulation.

Actual implementation will vary upon your events use case.

Download Now

Setup:

pip install gql oauthlib requests_oauthlib
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
import asyncio
import os

from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session

from gql import Client, gql
from gql.transport.websockets import WebsocketsTransport

CLIENT_ID = os.getenv("CLIENT_ID")
CLIENT_SECRET = os.getenv("CLIENT_SECRET")

TENANT_ID = os.getenv("TENANT_ID")
PAGE_SIZE = 10
MAX_ROWS = 50

QUERY_STR = "FROM process, persistence EARLIEST=-1d"

async def query(query_str: str, tenant_id: str, access_token: str):
    transport = WebsocketsTransport(
        url="wss://api.ctpx.secureworks.com/events/query",
        connect_args={
            "subprotocols": [
                "graphql-ws",
                f"x-tenant-context-{tenant_id}",
                f"access-token-{access_token}",
            ]
        },
    )

    async with Client(
        transport=transport,
        fetch_schema_from_transport=True,
    ) as session:

        subscription = gql(
            """subscription onEventQuery(
                            $query: String!,
                            $metadata: JSONObject,
                            $options: EventQueryOptions
                        ) {
                    eventQuery(query: $query, metadata: $metadata, options: $options) {
                        ...eventQueryResults
                        __typename
                    }
                }

                fragment eventQueryResults on EventQueryResults {
                    query {
                        id
                        query
                        status
                        reasons {
                            id
                            reason
                            __typename
                        }
                        submitted
                        completed
                        expires
                        types
                        metadata
                        __typename
                    }
                    result {
                        id
                        type
                        backend
                        status
                        reason
                        submitted
                        completed
                        expires
                        facets
                        rows
                        progress {
                            totalRows
                            totalRowsIsLowerBound
                            resultsTruncated
                            __typename
                        }
                        __typename
                    }
                    next
                    prev
                    __typename
                }"""
        )

        params = {
            "query": query_str,
            "options": {
                "timestampAscending": True,
                "pageSize": PAGE_SIZE,
                "maxRows": MAX_ROWS,
                "skipCache": True,
            },
        }

        results = []
        async for result in session.subscribe(subscription, variable_values=params):
            results.append(result)

    return results[:-1]


async def query_paginate(next_page: str, tenant_id: str, access_token: str):
    transport = WebsocketsTransport(
        url="wss://api.ctpx.secureworks.com/events/query",
        connect_args={
            "subprotocols": [
                "graphql-ws",
                f"x-tenant-context-{tenant_id}",
                f"access-token-{access_token}",
            ]
        },
    )

    async with Client(
        transport=transport,
        fetch_schema_from_transport=True,
    ) as session:

        subscription = gql(
            """subscription onEventPage($id: ID!) {
                eventPage(id: $id) {
                    ...eventQueryResults
                    __typename
                }
            }

            fragment eventQueryResults on EventQueryResults {
                query {
                    id
                    query
                    status
                    reasons {
                        id
                        reason
                        __typename
                    }
                    submitted
                    completed
                    expires
                    types
                    metadata
                    __typename
                }
                result {
                    id
                    type
                    backend
                    status
                    reason
                    submitted
                    completed
                    expires
                    facets
                    rows
                    progress {
                        totalRows
                        totalRowsIsLowerBound
                        resultsTruncated
                        __typename
                    }
                    __typename
                }
                next
                prev
                __typename
            }"""
        )

        params = {
            "id": next_page,
        }

        results = []
        async for result in session.subscribe(subscription, variable_values=params):
            results.append(result)

    return results[:-1]


if __name__ == "__main__":
    client = BackendApplicationClient(client_id=CLIENT_ID)
    oauth_client = OAuth2Session(client=client)
    token = oauth_client.fetch_token(
        token_url="https://api.ctpx.secureworks.com/auth/api/v2/auth/token",
        client_id=CLIENT_ID,
        client_secret=CLIENT_SECRET,
    )

    # for example, remove in implementation
    page = 1
    print("Page:", page)

    results = asyncio.run(
        query(
            query_str=QUERY_STR,
            tenant_id=TENANT_ID,
            access_token=token.get("access_token"),
        )
    )

    # for example
    print("# of result sets:", len(results))

    # For the example, we will place events in a schema keyed dictionary with the
    # data rows as list elements.
    #
    # Ex:
    #   events["process"] = [{"row1", "row2"}]
    #   events["auth] = [{"row1", "row2"}]
    events = {}
    for result in results:
        event_type = result.get("eventQuery", {}).get("result", {}).get("type", "error")
        
        # for example
        print(
            event_type,
            len(result.get("eventQuery", {}).get("result", {}).get("rows", [])),
        )
        
        events[event_type] = (
            result.get("eventQuery", {}).get("result", {}).get("rows", [])
        )

    # The next page reference can be in any or all of the returned result objects
    # There will be at most a single next page reference that we are extracting using set deduplication
    # Schemas that max out their results before another schema will not return a next reference
    next_page = list(
        {
            result.get("eventQuery", {}).get("next")
            for result in results
            if result.get("eventQuery", {}).get("next")
        }
    )
    while next_page:
        # for example
        page += 1
        print("Page:", page)
        print("Page Id:", next_page[0])
        
        results = asyncio.run(
            query_paginate(
                next_page=next_page[0],
                tenant_id=TENANT_ID,
                access_token=token.get("access_token"),
            )
        )

        print(len(results))

        for result in results:
            event_type = (
                result.get("eventPage", {}).get("result", {}).get("type", "error")
            )

            # for example
            print(
                event_type,
                len(result.get("eventPage", {}).get("result", {}).get("rows", [])),
            )

            events[event_type].extend(
                result.get("eventPage", {}).get("result", {}).get("rows", [])
            )

        # The next page reference can be in any or all of the returned result objects
        # There will be at most a single next page reference that we are extracting using set deduplication
        # Schemas that max out their results before another schema will not return a next reference
        next_page = list(
            {
                result.get("eventPage", {}).get("next")
                for result in results
                if result.get("eventPage", {}).get("next")
            }
        )

	# for example
    print()
    print("Result totals:")
    for key, results in events.items():
        print(key, len(results))

Example Output:

Page: 1
# of result sets: 2
persistence 6
process 10
Page: 2
Page Id: <query id>:1
# of result sets: 1
process 10
Page: 3
Page Id: <query id>:2
# of result sets: 1
process 10
Page: 4
Page Id: <query id>:3
# of result sets: 1
process 10
Page: 5
Page Id: <query id>:4
# of result sets: 1
process 9

Result totals:
persistence 6
process 49

Webhook Receiver (Javascript)

Using our orchestration and playbook systems, you can send alerts to a webhook receiver you run. For example this could be an AWS Lambda. Here is an example server.

This webhook receiver will:

  1. Listen for incoming alerts being sent
  2. Once an alert is received it will go fetch additional information about it for you to pass on to other downstream systems.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
const express = require('express');
const bodyParser = require('body-parser');
const redis = require("redis");
require('isomorphic-fetch');


const app = express()
const port = 8080

const query = `
  query alerts($input: GetByIDRequestInput!) {
    alertsServiceRetrieveAlertsById(in: $input) {
      status
      reason
      alerts {
        list {
             status
             resolution_reason
             suppressed
             metadata {
                 title
                 creator {
                    detector {
                        detector_id
                        detector_name
                        version
                    }
                 }
             }
        }
      }
    }
  }
`


const url = 'https://api.ctpx.secureworks.com/graphql'

getAlert = async(id) => {
    const variables = {
        input: {
            iDs: [id]
        }
    }

    const result = await fetch(url, {
        method: 'post',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer ' + process.env['ACCESS_TOKEN']
        },
        body: JSON.stringify({query, variables})
    })
        .then(response => response.json())
        .then(data => {
            return data
        })
        .catch((e) => {
            console.log(e)
        })
    return result.data.alertsServiceRetrieveAlertsById.alerts.list[0]
}

main = async() => {

    app.use(bodyParser.json())

    app.post('/', async (req, res) => {
        let evt = req.body
        let alert_id = evt['alert']['uuid']
			console.log(JSON.stringify(req.body))
			console.log(" event: ", evt['event'], " | alert: ", alert_id)
			// set alert in cache for 60 seconds, or refresh if seeing again
			// lookup alert
			const alert = await getAlert(alert_id)
			console.log(alert)
        })
        res.send('200')
    })

    app.listen(port, () => {
        console.log(`Example app listening at http://localhost:${port}`)
    })

}
main()