prelude-sdk-beta 1441__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prelude_sdk_beta-1441/LICENSE +9 -0
- prelude_sdk_beta-1441/PKG-INFO +46 -0
- prelude_sdk_beta-1441/README.md +30 -0
- prelude_sdk_beta-1441/prelude_sdk_beta/__init__.py +0 -0
- prelude_sdk_beta-1441/prelude_sdk_beta/controllers/__init__.py +0 -0
- prelude_sdk_beta-1441/prelude_sdk_beta/controllers/build_controller.py +315 -0
- prelude_sdk_beta-1441/prelude_sdk_beta/controllers/detect_controller.py +256 -0
- prelude_sdk_beta-1441/prelude_sdk_beta/controllers/export_controller.py +31 -0
- prelude_sdk_beta-1441/prelude_sdk_beta/controllers/generate_controller.py +40 -0
- prelude_sdk_beta-1441/prelude_sdk_beta/controllers/http_controller.py +83 -0
- prelude_sdk_beta-1441/prelude_sdk_beta/controllers/iam_controller.py +285 -0
- prelude_sdk_beta-1441/prelude_sdk_beta/controllers/jobs_controller.py +37 -0
- prelude_sdk_beta-1441/prelude_sdk_beta/controllers/partner_controller.py +154 -0
- prelude_sdk_beta-1441/prelude_sdk_beta/controllers/probe_controller.py +14 -0
- prelude_sdk_beta-1441/prelude_sdk_beta/controllers/scm_controller.py +595 -0
- prelude_sdk_beta-1441/prelude_sdk_beta/models/__init__.py +0 -0
- prelude_sdk_beta-1441/prelude_sdk_beta/models/account.py +269 -0
- prelude_sdk_beta-1441/prelude_sdk_beta/models/codes.py +564 -0
- prelude_sdk_beta-1441/prelude_sdk_beta.egg-info/PKG-INFO +46 -0
- prelude_sdk_beta-1441/prelude_sdk_beta.egg-info/SOURCES.txt +33 -0
- prelude_sdk_beta-1441/prelude_sdk_beta.egg-info/dependency_links.txt +1 -0
- prelude_sdk_beta-1441/prelude_sdk_beta.egg-info/requires.txt +1 -0
- prelude_sdk_beta-1441/prelude_sdk_beta.egg-info/top_level.txt +1 -0
- prelude_sdk_beta-1441/pyproject.toml +6 -0
- prelude_sdk_beta-1441/setup.cfg +24 -0
- prelude_sdk_beta-1441/tests/test_build.py +414 -0
- prelude_sdk_beta-1441/tests/test_detect.py +239 -0
- prelude_sdk_beta-1441/tests/test_generate.py +97 -0
- prelude_sdk_beta-1441/tests/test_iam.py +186 -0
- prelude_sdk_beta-1441/tests/test_partner.py +500 -0
- prelude_sdk_beta-1441/tests/test_probe.py +142 -0
- prelude_sdk_beta-1441/tests/test_scm.py +261 -0
- prelude_sdk_beta-1441/tests/test_scm_build.py +55 -0
- prelude_sdk_beta-1441/tests/testutils.py +66 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT LICENSE
|
|
2
|
+
|
|
3
|
+
Copyright 2022, Prelude Research
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: prelude-sdk-beta
|
|
3
|
+
Version: 1441
|
|
4
|
+
Summary: For interacting with the Prelude API
|
|
5
|
+
Home-page: https://github.com/preludeorg
|
|
6
|
+
Author: Prelude Research
|
|
7
|
+
Author-email: support@preludesecurity.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: requests
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# Prelude SDK
|
|
18
|
+
|
|
19
|
+
Interact with the Prelude Service API via Python.
|
|
20
|
+
|
|
21
|
+
> The prelude-cli utility wraps around this SDK to provide a rich command line experience.
|
|
22
|
+
|
|
23
|
+
Install this package to write your own tooling that works with Build or Detect functionality.
|
|
24
|
+
|
|
25
|
+
- IAM: manage your account
|
|
26
|
+
- Build: write and maintain your collection of security tests
|
|
27
|
+
- Detect: schedule security tests for your endpoints
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install prelude-sdk
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Documentation
|
|
36
|
+
|
|
37
|
+
TBD
|
|
38
|
+
|
|
39
|
+
## Testing
|
|
40
|
+
|
|
41
|
+
To test the Python SDK and Probes, run the following commands from the python/sdk/ directory:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install -r tests/requirements.txt
|
|
45
|
+
pytest tests --api https://api.preludesecurity.com --email <EMAIL>
|
|
46
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Prelude SDK
|
|
2
|
+
|
|
3
|
+
Interact with the Prelude Service API via Python.
|
|
4
|
+
|
|
5
|
+
> The prelude-cli utility wraps around this SDK to provide a rich command line experience.
|
|
6
|
+
|
|
7
|
+
Install this package to write your own tooling that works with Build or Detect functionality.
|
|
8
|
+
|
|
9
|
+
- IAM: manage your account
|
|
10
|
+
- Build: write and maintain your collection of security tests
|
|
11
|
+
- Detect: schedule security tests for your endpoints
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install prelude-sdk
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Documentation
|
|
20
|
+
|
|
21
|
+
TBD
|
|
22
|
+
|
|
23
|
+
## Testing
|
|
24
|
+
|
|
25
|
+
To test the Python SDK and Probes, run the following commands from the python/sdk/ directory:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install -r tests/requirements.txt
|
|
29
|
+
pytest tests --api https://api.preludesecurity.com --email <EMAIL>
|
|
30
|
+
```
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import urllib
|
|
2
|
+
|
|
3
|
+
from prelude_sdk_beta.controllers.http_controller import HttpController
|
|
4
|
+
from prelude_sdk_beta.models.account import verify_credentials
|
|
5
|
+
from prelude_sdk_beta.models.codes import Control, EDRResponse
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BuildController(HttpController):
|
|
9
|
+
|
|
10
|
+
def __init__(self, account):
|
|
11
|
+
super().__init__(account)
|
|
12
|
+
|
|
13
|
+
@verify_credentials
|
|
14
|
+
def clone_test(self, source_test_id):
|
|
15
|
+
"""Clone a test"""
|
|
16
|
+
res = self.post(
|
|
17
|
+
f"{self.account.hq}/build/tests",
|
|
18
|
+
json=dict(source_test_id=source_test_id),
|
|
19
|
+
headers=self.account.headers,
|
|
20
|
+
timeout=10,
|
|
21
|
+
)
|
|
22
|
+
return res.json()
|
|
23
|
+
|
|
24
|
+
@verify_credentials
|
|
25
|
+
def create_test(self, name, unit, technique=None, test_id=None):
|
|
26
|
+
"""Create or update a test"""
|
|
27
|
+
body = dict(name=name, unit=unit)
|
|
28
|
+
if technique:
|
|
29
|
+
body["technique"] = technique
|
|
30
|
+
if test_id:
|
|
31
|
+
body["id"] = test_id
|
|
32
|
+
|
|
33
|
+
res = self.post(
|
|
34
|
+
f"{self.account.hq}/build/tests",
|
|
35
|
+
json=body,
|
|
36
|
+
headers=self.account.headers,
|
|
37
|
+
timeout=10,
|
|
38
|
+
)
|
|
39
|
+
return res.json()
|
|
40
|
+
|
|
41
|
+
@verify_credentials
|
|
42
|
+
def update_test(
|
|
43
|
+
self,
|
|
44
|
+
test_id,
|
|
45
|
+
name=None,
|
|
46
|
+
unit=None,
|
|
47
|
+
technique=None,
|
|
48
|
+
crowdstrike_expected_outcome: EDRResponse = None,
|
|
49
|
+
):
|
|
50
|
+
"""Update a test"""
|
|
51
|
+
body = dict()
|
|
52
|
+
if crowdstrike_expected_outcome:
|
|
53
|
+
body["expected"] = dict(crowdstrike=crowdstrike_expected_outcome.value)
|
|
54
|
+
if name:
|
|
55
|
+
body["name"] = name
|
|
56
|
+
if unit:
|
|
57
|
+
body["unit"] = unit
|
|
58
|
+
if technique is not None:
|
|
59
|
+
body["technique"] = technique
|
|
60
|
+
|
|
61
|
+
res = self.post(
|
|
62
|
+
f"{self.account.hq}/build/tests/{test_id}",
|
|
63
|
+
json=body,
|
|
64
|
+
headers=self.account.headers,
|
|
65
|
+
timeout=10,
|
|
66
|
+
)
|
|
67
|
+
return res.json()
|
|
68
|
+
|
|
69
|
+
@verify_credentials
|
|
70
|
+
def delete_test(self, test_id, purge):
|
|
71
|
+
"""Delete an existing test"""
|
|
72
|
+
res = self.delete(
|
|
73
|
+
f"{self.account.hq}/build/tests/{test_id}",
|
|
74
|
+
json=dict(purge=purge),
|
|
75
|
+
headers=self.account.headers,
|
|
76
|
+
timeout=10,
|
|
77
|
+
)
|
|
78
|
+
return res.json()
|
|
79
|
+
|
|
80
|
+
@verify_credentials
|
|
81
|
+
def undelete_test(self, test_id):
|
|
82
|
+
"""Undelete a tombstoned test"""
|
|
83
|
+
res = self.post(
|
|
84
|
+
f"{self.account.hq}/build/tests/{test_id}/undelete",
|
|
85
|
+
headers=self.account.headers,
|
|
86
|
+
timeout=10,
|
|
87
|
+
)
|
|
88
|
+
return res.json()
|
|
89
|
+
|
|
90
|
+
@verify_credentials
|
|
91
|
+
def upload(self, test_id, filename, data, skip_compile=False):
|
|
92
|
+
"""Upload a test or attachment"""
|
|
93
|
+
if len(data) > 1000000:
|
|
94
|
+
raise ValueError(f"File size must be under 1MB ({filename})")
|
|
95
|
+
|
|
96
|
+
h = self.account.headers | {"Content-Type": "application/octet-stream"}
|
|
97
|
+
query_params = ""
|
|
98
|
+
if skip_compile:
|
|
99
|
+
query_params = "?" + urllib.parse.urlencode(dict(skip_compile=True))
|
|
100
|
+
res = self.post(
|
|
101
|
+
f"{self.account.hq}/build/tests/{test_id}/{filename}{query_params}",
|
|
102
|
+
data=data,
|
|
103
|
+
headers=h,
|
|
104
|
+
timeout=10,
|
|
105
|
+
)
|
|
106
|
+
return res.json()
|
|
107
|
+
|
|
108
|
+
@verify_credentials
|
|
109
|
+
def compile_code_string(self, code: str, source_test_id: str = None):
|
|
110
|
+
"""Compile a code string"""
|
|
111
|
+
res = self.post(
|
|
112
|
+
f"{self.account.hq}/build/compile",
|
|
113
|
+
json=dict(code=code, source_test_id=source_test_id),
|
|
114
|
+
headers=self.account.headers,
|
|
115
|
+
timeout=10,
|
|
116
|
+
)
|
|
117
|
+
return res.json()
|
|
118
|
+
|
|
119
|
+
@verify_credentials
|
|
120
|
+
def get_compile_status(self, job_id):
|
|
121
|
+
res = self.get(
|
|
122
|
+
f"{self.account.hq}/build/compile/{job_id}",
|
|
123
|
+
headers=self.account.headers,
|
|
124
|
+
timeout=10,
|
|
125
|
+
)
|
|
126
|
+
return res.json()
|
|
127
|
+
|
|
128
|
+
@verify_credentials
|
|
129
|
+
def create_threat(
|
|
130
|
+
self, name, published, threat_id=None, source_id=None, source=None, tests=None
|
|
131
|
+
):
|
|
132
|
+
"""Create a threat"""
|
|
133
|
+
body = dict(name=name, published=published)
|
|
134
|
+
if threat_id:
|
|
135
|
+
body["id"] = threat_id
|
|
136
|
+
if source_id:
|
|
137
|
+
body["source_id"] = source_id
|
|
138
|
+
if source:
|
|
139
|
+
body["source"] = source
|
|
140
|
+
if tests:
|
|
141
|
+
body["tests"] = tests
|
|
142
|
+
|
|
143
|
+
res = self.post(
|
|
144
|
+
f"{self.account.hq}/build/threats",
|
|
145
|
+
json=body,
|
|
146
|
+
headers=self.account.headers,
|
|
147
|
+
timeout=10,
|
|
148
|
+
)
|
|
149
|
+
return res.json()
|
|
150
|
+
|
|
151
|
+
@verify_credentials
|
|
152
|
+
def update_threat(
|
|
153
|
+
self,
|
|
154
|
+
threat_id,
|
|
155
|
+
name=None,
|
|
156
|
+
source_id=None,
|
|
157
|
+
source=None,
|
|
158
|
+
published=None,
|
|
159
|
+
tests=None,
|
|
160
|
+
):
|
|
161
|
+
"""Update a threat"""
|
|
162
|
+
body = dict()
|
|
163
|
+
if name:
|
|
164
|
+
body["name"] = name
|
|
165
|
+
if source_id is not None:
|
|
166
|
+
body["source_id"] = source_id
|
|
167
|
+
if source is not None:
|
|
168
|
+
body["source"] = source
|
|
169
|
+
if published is not None:
|
|
170
|
+
body["published"] = published
|
|
171
|
+
if tests is not None:
|
|
172
|
+
body["tests"] = tests
|
|
173
|
+
|
|
174
|
+
res = self.post(
|
|
175
|
+
f"{self.account.hq}/build/threats/{threat_id}",
|
|
176
|
+
json=body,
|
|
177
|
+
headers=self.account.headers,
|
|
178
|
+
timeout=10,
|
|
179
|
+
)
|
|
180
|
+
return res.json()
|
|
181
|
+
|
|
182
|
+
@verify_credentials
|
|
183
|
+
def delete_threat(self, threat_id, purge):
|
|
184
|
+
"""Delete an existing threat"""
|
|
185
|
+
res = self.delete(
|
|
186
|
+
f"{self.account.hq}/build/threats/{threat_id}",
|
|
187
|
+
json=dict(purge=purge),
|
|
188
|
+
headers=self.account.headers,
|
|
189
|
+
timeout=10,
|
|
190
|
+
)
|
|
191
|
+
return res.json()
|
|
192
|
+
|
|
193
|
+
@verify_credentials
|
|
194
|
+
def undelete_threat(self, threat_id):
|
|
195
|
+
"""Undelete a tombstoned threat"""
|
|
196
|
+
res = self.post(
|
|
197
|
+
f"{self.account.hq}/build/threats/{threat_id}/undelete",
|
|
198
|
+
headers=self.account.headers,
|
|
199
|
+
timeout=10,
|
|
200
|
+
)
|
|
201
|
+
return res.json()
|
|
202
|
+
|
|
203
|
+
@verify_credentials
|
|
204
|
+
def create_detection(
|
|
205
|
+
self, rule: str, test_id: str, detection_id=None, rule_id=None
|
|
206
|
+
):
|
|
207
|
+
"""Create a detection"""
|
|
208
|
+
body = dict(rule=rule, test_id=test_id)
|
|
209
|
+
if detection_id:
|
|
210
|
+
body["detection_id"] = detection_id
|
|
211
|
+
if rule_id:
|
|
212
|
+
body["rule_id"] = rule_id
|
|
213
|
+
|
|
214
|
+
res = self.post(
|
|
215
|
+
f"{self.account.hq}/build/detections",
|
|
216
|
+
json=body,
|
|
217
|
+
headers=self.account.headers,
|
|
218
|
+
timeout=10,
|
|
219
|
+
)
|
|
220
|
+
return res.json()
|
|
221
|
+
|
|
222
|
+
@verify_credentials
|
|
223
|
+
def update_detection(self, detection_id: str, rule=None, test_id=None):
|
|
224
|
+
"""Update a detection"""
|
|
225
|
+
body = dict()
|
|
226
|
+
if rule:
|
|
227
|
+
body["rule"] = rule
|
|
228
|
+
if test_id:
|
|
229
|
+
body["test_id"] = test_id
|
|
230
|
+
|
|
231
|
+
res = self.post(
|
|
232
|
+
f"{self.account.hq}/build/detections/{detection_id}",
|
|
233
|
+
json=body,
|
|
234
|
+
headers=self.account.headers,
|
|
235
|
+
timeout=10,
|
|
236
|
+
)
|
|
237
|
+
return res.json()
|
|
238
|
+
|
|
239
|
+
@verify_credentials
|
|
240
|
+
def delete_detection(self, detection_id: str):
|
|
241
|
+
"""Delete an existing detection"""
|
|
242
|
+
res = self.delete(
|
|
243
|
+
f"{self.account.hq}/build/detections/{detection_id}",
|
|
244
|
+
headers=self.account.headers,
|
|
245
|
+
timeout=10,
|
|
246
|
+
)
|
|
247
|
+
return res.json()
|
|
248
|
+
|
|
249
|
+
@verify_credentials
|
|
250
|
+
def create_threat_hunt(
|
|
251
|
+
self,
|
|
252
|
+
control: Control,
|
|
253
|
+
test_id: str,
|
|
254
|
+
name: str,
|
|
255
|
+
query: str,
|
|
256
|
+
threat_hunt_id: str = None,
|
|
257
|
+
):
|
|
258
|
+
"""Create a threat hunt"""
|
|
259
|
+
body = dict(
|
|
260
|
+
control=control.name,
|
|
261
|
+
name=name,
|
|
262
|
+
query=query,
|
|
263
|
+
test_id=test_id,
|
|
264
|
+
)
|
|
265
|
+
if threat_hunt_id:
|
|
266
|
+
body["id"] = threat_hunt_id
|
|
267
|
+
|
|
268
|
+
res = self.post(
|
|
269
|
+
f"{self.account.hq}/build/threat_hunts",
|
|
270
|
+
json=body,
|
|
271
|
+
headers=self.account.headers,
|
|
272
|
+
timeout=10,
|
|
273
|
+
)
|
|
274
|
+
threat_hunt = res.json()
|
|
275
|
+
if self.account.resolve_enums:
|
|
276
|
+
self.resolve_enums(threat_hunt, [(Control, "control")])
|
|
277
|
+
return threat_hunt
|
|
278
|
+
|
|
279
|
+
@verify_credentials
|
|
280
|
+
def update_threat_hunt(
|
|
281
|
+
self,
|
|
282
|
+
threat_hunt_id: str,
|
|
283
|
+
name: str = None,
|
|
284
|
+
query: str = None,
|
|
285
|
+
test_id: str = None,
|
|
286
|
+
):
|
|
287
|
+
"""Update a threat hunt"""
|
|
288
|
+
body = dict()
|
|
289
|
+
if name:
|
|
290
|
+
body["name"] = name
|
|
291
|
+
if query:
|
|
292
|
+
body["query"] = query
|
|
293
|
+
if test_id:
|
|
294
|
+
body["test_id"] = test_id
|
|
295
|
+
|
|
296
|
+
res = self.post(
|
|
297
|
+
f"{self.account.hq}/build/threat_hunts/{threat_hunt_id}",
|
|
298
|
+
json=body,
|
|
299
|
+
headers=self.account.headers,
|
|
300
|
+
timeout=10,
|
|
301
|
+
)
|
|
302
|
+
threat_hunt = res.json()
|
|
303
|
+
if self.account.resolve_enums:
|
|
304
|
+
self.resolve_enums(threat_hunt, [(Control, "control")])
|
|
305
|
+
return threat_hunt
|
|
306
|
+
|
|
307
|
+
@verify_credentials
|
|
308
|
+
def delete_threat_hunt(self, threat_hunt_id: str):
|
|
309
|
+
"""Delete an existing threat hunt"""
|
|
310
|
+
res = self.delete(
|
|
311
|
+
f"{self.account.hq}/build/threat_hunts/{threat_hunt_id}",
|
|
312
|
+
headers=self.account.headers,
|
|
313
|
+
timeout=10,
|
|
314
|
+
)
|
|
315
|
+
return res.json()
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
from prelude_sdk_beta.controllers.http_controller import HttpController
|
|
2
|
+
from prelude_sdk_beta.models.account import verify_credentials
|
|
3
|
+
from prelude_sdk_beta.models.codes import Control, RunCode
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DetectController(HttpController):
|
|
7
|
+
|
|
8
|
+
def __init__(self, account):
|
|
9
|
+
super().__init__(account)
|
|
10
|
+
|
|
11
|
+
def register_endpoint(self, host, serial_num, reg_string, tags=None):
|
|
12
|
+
"""Register (or re-register) an endpoint to your account"""
|
|
13
|
+
body = dict(id=f"{host}:{serial_num}")
|
|
14
|
+
if tags:
|
|
15
|
+
body["tags"] = tags
|
|
16
|
+
account, token = reg_string.split("/")
|
|
17
|
+
|
|
18
|
+
res = self._session.post(
|
|
19
|
+
f"{self.account.hq}/detect/endpoint",
|
|
20
|
+
headers=dict(account=account, token=token, _product="py-sdk"),
|
|
21
|
+
json=body,
|
|
22
|
+
timeout=10,
|
|
23
|
+
)
|
|
24
|
+
return res.text
|
|
25
|
+
|
|
26
|
+
@verify_credentials
|
|
27
|
+
def update_endpoint(self, endpoint_id, tags=None):
|
|
28
|
+
"""Update an endpoint in your account"""
|
|
29
|
+
body = dict()
|
|
30
|
+
if tags is not None:
|
|
31
|
+
body["tags"] = tags
|
|
32
|
+
|
|
33
|
+
res = self.post(
|
|
34
|
+
f"{self.account.hq}/detect/endpoint/{endpoint_id}",
|
|
35
|
+
headers=self.account.headers,
|
|
36
|
+
json=body,
|
|
37
|
+
timeout=10,
|
|
38
|
+
)
|
|
39
|
+
return res.json()
|
|
40
|
+
|
|
41
|
+
@verify_credentials
|
|
42
|
+
def delete_endpoint(self, ident: str):
|
|
43
|
+
"""Delete an endpoint from your account"""
|
|
44
|
+
params = dict(id=ident)
|
|
45
|
+
res = self.delete(
|
|
46
|
+
f"{self.account.hq}/detect/endpoint",
|
|
47
|
+
headers=self.account.headers,
|
|
48
|
+
json=params,
|
|
49
|
+
timeout=10,
|
|
50
|
+
)
|
|
51
|
+
return res.json()
|
|
52
|
+
|
|
53
|
+
@verify_credentials
|
|
54
|
+
def list_endpoints(self, days: int = 90):
|
|
55
|
+
"""List all endpoints on your account"""
|
|
56
|
+
params = dict(days=days)
|
|
57
|
+
res = self.get(
|
|
58
|
+
f"{self.account.hq}/detect/endpoint",
|
|
59
|
+
headers=self.account.headers,
|
|
60
|
+
params=params,
|
|
61
|
+
timeout=10,
|
|
62
|
+
)
|
|
63
|
+
endpoints = res.json()
|
|
64
|
+
if self.account.resolve_enums:
|
|
65
|
+
self.resolve_enums(endpoints, [(Control, "control")])
|
|
66
|
+
return endpoints
|
|
67
|
+
|
|
68
|
+
@verify_credentials
|
|
69
|
+
def describe_activity(self, filters: dict, view: str = "protected"):
|
|
70
|
+
"""Get report for an Account"""
|
|
71
|
+
params = dict(view=view, **filters)
|
|
72
|
+
res = self.get(
|
|
73
|
+
f"{self.account.hq}/detect/activity",
|
|
74
|
+
headers=self.account.headers,
|
|
75
|
+
params=params,
|
|
76
|
+
timeout=10,
|
|
77
|
+
)
|
|
78
|
+
return res.json()
|
|
79
|
+
|
|
80
|
+
@verify_credentials
|
|
81
|
+
def threat_hunt_activity(self, threat_hunt_id=None, test_id=None, threat_id=None):
|
|
82
|
+
"""Get threat hunt activity"""
|
|
83
|
+
filters = dict(
|
|
84
|
+
threat_hunt_id=threat_hunt_id, test_id=test_id, threat_id=threat_id
|
|
85
|
+
)
|
|
86
|
+
res = self.get(
|
|
87
|
+
f"{self.account.hq}/detect/threat_hunt_activity",
|
|
88
|
+
headers=self.account.headers,
|
|
89
|
+
params=filters,
|
|
90
|
+
timeout=10,
|
|
91
|
+
)
|
|
92
|
+
return res.json()
|
|
93
|
+
|
|
94
|
+
@verify_credentials
|
|
95
|
+
def list_tests(self, filters: dict = None):
|
|
96
|
+
"""List all tests available to an account"""
|
|
97
|
+
res = self.get(
|
|
98
|
+
f"{self.account.hq}/detect/tests",
|
|
99
|
+
headers=self.account.headers,
|
|
100
|
+
params=filters if filters else {},
|
|
101
|
+
timeout=10,
|
|
102
|
+
)
|
|
103
|
+
return res.json()
|
|
104
|
+
|
|
105
|
+
@verify_credentials
|
|
106
|
+
def get_test(self, test_id):
|
|
107
|
+
"""Get properties of an existing test"""
|
|
108
|
+
res = self.get(
|
|
109
|
+
f"{self.account.hq}/detect/tests/{test_id}",
|
|
110
|
+
headers=self.account.headers,
|
|
111
|
+
timeout=10,
|
|
112
|
+
)
|
|
113
|
+
return res.json()
|
|
114
|
+
|
|
115
|
+
@verify_credentials
|
|
116
|
+
def list_techniques(self):
|
|
117
|
+
"""List techniques"""
|
|
118
|
+
res = self.get(
|
|
119
|
+
f"{self.account.hq}/detect/techniques",
|
|
120
|
+
headers=self.account.headers,
|
|
121
|
+
timeout=10,
|
|
122
|
+
)
|
|
123
|
+
return res.json()
|
|
124
|
+
|
|
125
|
+
@verify_credentials
|
|
126
|
+
def list_threats(self):
|
|
127
|
+
"""List threats"""
|
|
128
|
+
res = self.get(
|
|
129
|
+
f"{self.account.hq}/detect/threats",
|
|
130
|
+
headers=self.account.headers,
|
|
131
|
+
params={},
|
|
132
|
+
timeout=10,
|
|
133
|
+
)
|
|
134
|
+
return res.json()
|
|
135
|
+
|
|
136
|
+
@verify_credentials
|
|
137
|
+
def get_threat(self, threat_id):
|
|
138
|
+
"""Get properties of an existing threat"""
|
|
139
|
+
res = self.get(
|
|
140
|
+
f"{self.account.hq}/detect/threats/{threat_id}",
|
|
141
|
+
headers=self.account.headers,
|
|
142
|
+
timeout=10,
|
|
143
|
+
)
|
|
144
|
+
return res.json()
|
|
145
|
+
|
|
146
|
+
@verify_credentials
|
|
147
|
+
def list_detections(self):
|
|
148
|
+
"""List detections"""
|
|
149
|
+
res = self.get(
|
|
150
|
+
f"{self.account.hq}/detect/detections",
|
|
151
|
+
headers=self.account.headers,
|
|
152
|
+
params={},
|
|
153
|
+
timeout=10,
|
|
154
|
+
)
|
|
155
|
+
return res.json()
|
|
156
|
+
|
|
157
|
+
@verify_credentials
|
|
158
|
+
def get_detection(self, detection_id):
|
|
159
|
+
"""Get properties of an existing detection"""
|
|
160
|
+
res = self.get(
|
|
161
|
+
f"{self.account.hq}/detect/detections/{detection_id}",
|
|
162
|
+
headers=self.account.headers,
|
|
163
|
+
timeout=10,
|
|
164
|
+
)
|
|
165
|
+
return res.json()
|
|
166
|
+
|
|
167
|
+
@verify_credentials
|
|
168
|
+
def list_threat_hunts(self, filters: dict = None):
|
|
169
|
+
"""List threat hunts"""
|
|
170
|
+
res = self.get(
|
|
171
|
+
f"{self.account.hq}/detect/threat_hunts",
|
|
172
|
+
headers=self.account.headers,
|
|
173
|
+
params=filters if filters else {},
|
|
174
|
+
timeout=10,
|
|
175
|
+
)
|
|
176
|
+
threat_hunts = res.json()
|
|
177
|
+
if self.account.resolve_enums:
|
|
178
|
+
self.resolve_enums(threat_hunts, [(Control, "control")])
|
|
179
|
+
return threat_hunts
|
|
180
|
+
|
|
181
|
+
@verify_credentials
|
|
182
|
+
def get_threat_hunt(self, threat_hunt_id):
|
|
183
|
+
"""Get properties of an existing threat hunt"""
|
|
184
|
+
res = self.get(
|
|
185
|
+
f"{self.account.hq}/detect/threat_hunts/{threat_hunt_id}",
|
|
186
|
+
headers=self.account.headers,
|
|
187
|
+
timeout=10,
|
|
188
|
+
)
|
|
189
|
+
threat_hunt = res.json()
|
|
190
|
+
if self.account.resolve_enums:
|
|
191
|
+
self.resolve_enums(threat_hunt, [(Control, "control")])
|
|
192
|
+
return threat_hunt
|
|
193
|
+
|
|
194
|
+
@verify_credentials
|
|
195
|
+
def do_threat_hunt(self, threat_hunt_id):
|
|
196
|
+
"""Run a threat hunt"""
|
|
197
|
+
res = self.post(
|
|
198
|
+
f"{self.account.hq}/detect/threat_hunts/{threat_hunt_id}",
|
|
199
|
+
headers=self.account.headers,
|
|
200
|
+
timeout=10,
|
|
201
|
+
)
|
|
202
|
+
return res.json()
|
|
203
|
+
|
|
204
|
+
@verify_credentials
|
|
205
|
+
def download(self, test_id, filename):
|
|
206
|
+
"""Clone a test file or attachment"""
|
|
207
|
+
res = self.get(
|
|
208
|
+
f"{self.account.hq}/detect/tests/{test_id}/{filename}",
|
|
209
|
+
headers=self.account.headers,
|
|
210
|
+
timeout=10,
|
|
211
|
+
)
|
|
212
|
+
return res.content
|
|
213
|
+
|
|
214
|
+
@verify_credentials
|
|
215
|
+
def schedule(self, items: list):
|
|
216
|
+
"""Schedule tests and threats so endpoints will start running them
|
|
217
|
+
|
|
218
|
+
Example: items=[dict(run_code='DAILY', tags='grp-1,grp2', test_id='123-123-123'),
|
|
219
|
+
dict(run_code='DAILY', tags='grp-1', threat_id='abc-def-ghi')]
|
|
220
|
+
"""
|
|
221
|
+
res = self.post(
|
|
222
|
+
url=f"{self.account.hq}/detect/queue",
|
|
223
|
+
headers=self.account.headers,
|
|
224
|
+
json=dict(items=items),
|
|
225
|
+
timeout=10,
|
|
226
|
+
)
|
|
227
|
+
schedule = res.json()
|
|
228
|
+
if self.account.resolve_enums:
|
|
229
|
+
self.resolve_enums(schedule, [(RunCode, "run_code")])
|
|
230
|
+
return schedule
|
|
231
|
+
|
|
232
|
+
@verify_credentials
|
|
233
|
+
def unschedule(self, items: list):
|
|
234
|
+
"""Unschedule tests and threats so endpoints will stop running them
|
|
235
|
+
|
|
236
|
+
Example: items=[dict(tags='grp-1,grp2', test_id='123-123-123'),
|
|
237
|
+
dict(tags='grp-1', threat_id='abc-def-ghi')]
|
|
238
|
+
"""
|
|
239
|
+
res = self.delete(
|
|
240
|
+
f"{self.account.hq}/detect/queue",
|
|
241
|
+
headers=self.account.headers,
|
|
242
|
+
json=dict(items=items),
|
|
243
|
+
timeout=10,
|
|
244
|
+
)
|
|
245
|
+
return res.json()
|
|
246
|
+
|
|
247
|
+
@verify_credentials
|
|
248
|
+
def accept_terms(self, name, version):
|
|
249
|
+
"""Accept terms and conditions"""
|
|
250
|
+
res = self.post(
|
|
251
|
+
f"{self.account.hq}/iam/terms",
|
|
252
|
+
headers=self.account.headers,
|
|
253
|
+
json=dict(name=name, version=version),
|
|
254
|
+
timeout=10,
|
|
255
|
+
)
|
|
256
|
+
return res.json()
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from prelude_sdk_beta.controllers.http_controller import HttpController
|
|
2
|
+
from prelude_sdk_beta.models.account import verify_credentials
|
|
3
|
+
from prelude_sdk_beta.models.codes import SCMCategory
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ExportController(HttpController):
|
|
7
|
+
|
|
8
|
+
def __init__(self, account):
|
|
9
|
+
super().__init__(account)
|
|
10
|
+
|
|
11
|
+
@verify_credentials
|
|
12
|
+
def export_scm(
|
|
13
|
+
self,
|
|
14
|
+
export_type: SCMCategory,
|
|
15
|
+
filter: str = None,
|
|
16
|
+
orderby: str = None,
|
|
17
|
+
top: int = None,
|
|
18
|
+
):
|
|
19
|
+
"""Download partner data as a CSV"""
|
|
20
|
+
params = {
|
|
21
|
+
"$filter": filter,
|
|
22
|
+
"$orderby": orderby,
|
|
23
|
+
"$top": top,
|
|
24
|
+
}
|
|
25
|
+
res = self.post(
|
|
26
|
+
f"{self.account.hq}/export/scm/{export_type.name}",
|
|
27
|
+
params=params,
|
|
28
|
+
headers=self.account.headers,
|
|
29
|
+
timeout=10,
|
|
30
|
+
)
|
|
31
|
+
return res.json()
|