mantis_api_client 5.5.0__py3-none-any.whl
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.
- mantis_api_client/__init__.py +21 -0
- mantis_api_client/cli_parser/account_parser.py +451 -0
- mantis_api_client/cli_parser/attack_parser.py +317 -0
- mantis_api_client/cli_parser/bas_parser.py +1204 -0
- mantis_api_client/cli_parser/basebox_parser.py +258 -0
- mantis_api_client/cli_parser/dataset_parser.py +200 -0
- mantis_api_client/cli_parser/lab_parser.py +805 -0
- mantis_api_client/cli_parser/labs_parser.py +87 -0
- mantis_api_client/cli_parser/log_collector_parser.py +71 -0
- mantis_api_client/cli_parser/redteam_parser.py +375 -0
- mantis_api_client/cli_parser/scenario_parser.py +311 -0
- mantis_api_client/cli_parser/signature_parser.py +147 -0
- mantis_api_client/cli_parser/topology_parser.py +255 -0
- mantis_api_client/cli_parser/user_parser.py +225 -0
- mantis_api_client/config.py +73 -0
- mantis_api_client/dataset_api.py +267 -0
- mantis_api_client/exceptions.py +27 -0
- mantis_api_client/mantis.py +186 -0
- mantis_api_client/oidc.py +302 -0
- mantis_api_client/scenario_api.py +1196 -0
- mantis_api_client/user_api.py +282 -0
- mantis_api_client/utils.py +130 -0
- mantis_api_client-5.5.0.dist-info/AUTHORS +1 -0
- mantis_api_client-5.5.0.dist-info/LICENSE +19 -0
- mantis_api_client-5.5.0.dist-info/METADATA +33 -0
- mantis_api_client-5.5.0.dist-info/RECORD +28 -0
- mantis_api_client-5.5.0.dist-info/WHEEL +4 -0
- mantis_api_client-5.5.0.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,1196 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
from typing import Any
|
|
4
|
+
from typing import Dict
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
from mantis_common_model.pagination import Pagination
|
|
9
|
+
from mantis_scenario_model.api_scenario_model import LabListReply
|
|
10
|
+
from mantis_scenario_model.api_scenario_model import PausedStatus
|
|
11
|
+
from mantis_scenario_model.api_scenario_model import RemoteAccess
|
|
12
|
+
from mantis_scenario_model.basebox_model import Basebox
|
|
13
|
+
from mantis_scenario_model.lab_config_model import ContentType
|
|
14
|
+
from mantis_scenario_model.lab_config_model import LabConfig
|
|
15
|
+
from mantis_scenario_model.log_collector_model import LogCollector
|
|
16
|
+
from mantis_scenario_model.scenario_model import Scenario
|
|
17
|
+
from mantis_scenario_model.security_alert_model import SecurityAlert
|
|
18
|
+
from mantis_scenario_model.signature_model import Signature
|
|
19
|
+
from mantis_scenario_model.topology_model import Topology
|
|
20
|
+
from mantis_scenario_model.unit_attack_model import UnitAttack
|
|
21
|
+
from pydantic import parse_obj_as
|
|
22
|
+
|
|
23
|
+
from mantis_api_client.config import mantis_api_client_config
|
|
24
|
+
from mantis_api_client.oidc import authorize
|
|
25
|
+
from mantis_api_client.oidc import get_oidc_client
|
|
26
|
+
|
|
27
|
+
# -------------------------------------------------------------------------- #
|
|
28
|
+
# Internal helpers
|
|
29
|
+
# -------------------------------------------------------------------------- #
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _get(route: str, **kwargs: Any) -> requests.Response:
|
|
33
|
+
return requests.get(
|
|
34
|
+
f"{mantis_api_client_config.scenario_api_url}/lab{route}",
|
|
35
|
+
verify=mantis_api_client_config.cacert,
|
|
36
|
+
cert=(mantis_api_client_config.cert, mantis_api_client_config.key),
|
|
37
|
+
**kwargs,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _get_agent(route: str, **kwargs: Any) -> requests.Response:
|
|
42
|
+
return requests.get(
|
|
43
|
+
f"{mantis_api_client_config.scenario_api_url}/bas_agent{route}",
|
|
44
|
+
verify=mantis_api_client_config.cacert,
|
|
45
|
+
cert=(mantis_api_client_config.cert, mantis_api_client_config.key),
|
|
46
|
+
**kwargs,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _post(route: str, **kwargs: Any) -> requests.Response:
|
|
51
|
+
return requests.post(
|
|
52
|
+
f"{mantis_api_client_config.scenario_api_url}/lab{route}",
|
|
53
|
+
verify=mantis_api_client_config.cacert,
|
|
54
|
+
cert=(mantis_api_client_config.cert, mantis_api_client_config.key),
|
|
55
|
+
**kwargs,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _put(route: str, **kwargs: Any) -> requests.Response:
|
|
60
|
+
return requests.put(
|
|
61
|
+
f"{mantis_api_client_config.scenario_api_url}/lab{route}",
|
|
62
|
+
verify=mantis_api_client_config.cacert,
|
|
63
|
+
cert=(mantis_api_client_config.cert, mantis_api_client_config.key),
|
|
64
|
+
**kwargs,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _delete(route: str, **kwargs: Any) -> requests.Response:
|
|
69
|
+
return requests.delete(
|
|
70
|
+
f"{mantis_api_client_config.scenario_api_url}/lab{route}",
|
|
71
|
+
verify=mantis_api_client_config.cacert,
|
|
72
|
+
cert=(mantis_api_client_config.cert, mantis_api_client_config.key),
|
|
73
|
+
**kwargs,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _handle_error(
|
|
78
|
+
result: requests.Response, context_error_msg: str
|
|
79
|
+
) -> requests.Response:
|
|
80
|
+
error_msg = ""
|
|
81
|
+
if result.headers.get("content-type") == "application/json":
|
|
82
|
+
error_data = result.json()
|
|
83
|
+
for k in ("message", "detail"):
|
|
84
|
+
if k in error_data:
|
|
85
|
+
error_msg = error_data[k]
|
|
86
|
+
break
|
|
87
|
+
if not error_msg:
|
|
88
|
+
error_msg = result.text
|
|
89
|
+
|
|
90
|
+
raise Exception(
|
|
91
|
+
f"{context_error_msg}. "
|
|
92
|
+
f"Status code: '{result.status_code}'.\n"
|
|
93
|
+
f"Error message: '{error_msg}'."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# -------------------------------------------------------------------------- #
|
|
98
|
+
# Internal helpers
|
|
99
|
+
# -------------------------------------------------------------------------- #
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def get_version() -> str:
|
|
103
|
+
"""
|
|
104
|
+
Return Scenario API version.
|
|
105
|
+
|
|
106
|
+
:return: The version number is a string
|
|
107
|
+
"""
|
|
108
|
+
result = authorize(_get)("/version")
|
|
109
|
+
|
|
110
|
+
if result.status_code != 200:
|
|
111
|
+
_handle_error(result, "Cannot retrieve scenario API version")
|
|
112
|
+
|
|
113
|
+
return result.json()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def get_cyberrange_version() -> str:
|
|
117
|
+
"""
|
|
118
|
+
Return Cyber Range version launchable via Scenario API.
|
|
119
|
+
|
|
120
|
+
:return: The version number is a string
|
|
121
|
+
"""
|
|
122
|
+
result = authorize(_get)("/version/cyberrange")
|
|
123
|
+
|
|
124
|
+
if result.status_code != 200:
|
|
125
|
+
_handle_error(result, "Cannot retrieve Cyber Range version")
|
|
126
|
+
|
|
127
|
+
return result.json()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# -------------------------------------------------------------------------- #
|
|
131
|
+
# Scenario API
|
|
132
|
+
# -------------------------------------------------------------------------- #
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def fetch_attacks() -> Any:
|
|
136
|
+
"""
|
|
137
|
+
List all available unit attacks
|
|
138
|
+
|
|
139
|
+
:return: the list of unit attacks
|
|
140
|
+
"""
|
|
141
|
+
result = authorize(_get)("/unit_attack/")
|
|
142
|
+
|
|
143
|
+
if result.status_code != 200:
|
|
144
|
+
_handle_error(result, "Cannot retrieve unit attacks from scenario API")
|
|
145
|
+
else:
|
|
146
|
+
attacks_data = result.json()
|
|
147
|
+
attacks = parse_obj_as(List[UnitAttack], attacks_data)
|
|
148
|
+
|
|
149
|
+
return attacks
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def fetch_attack_by_name(attack_name: str) -> Any:
|
|
153
|
+
"""
|
|
154
|
+
Get the full JSON manifest of a specific unit attack
|
|
155
|
+
|
|
156
|
+
:param attack_name: the name of the unit attack to fetch
|
|
157
|
+
|
|
158
|
+
:return: the JSON of unit attacks
|
|
159
|
+
"""
|
|
160
|
+
result = authorize(_get)(f"/unit_attack/{attack_name}")
|
|
161
|
+
|
|
162
|
+
if result.status_code != 200:
|
|
163
|
+
_handle_error(
|
|
164
|
+
result,
|
|
165
|
+
f"Cannot retrieve unit attack '{attack_name}' from frontend publish API",
|
|
166
|
+
)
|
|
167
|
+
else:
|
|
168
|
+
attack_data = result.json()
|
|
169
|
+
attack = UnitAttack(**attack_data)
|
|
170
|
+
|
|
171
|
+
return attack
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def fetch_scenarios() -> List[Scenario]:
|
|
175
|
+
"""
|
|
176
|
+
List all available scenarios
|
|
177
|
+
|
|
178
|
+
:return: the list of scenarios
|
|
179
|
+
"""
|
|
180
|
+
result = authorize(_get)("/scenario/")
|
|
181
|
+
|
|
182
|
+
if result.status_code != 200:
|
|
183
|
+
_handle_error(result, "Cannot retrieve scenarios from scenario API")
|
|
184
|
+
else:
|
|
185
|
+
scenarios_data = result.json()
|
|
186
|
+
scenarios = parse_obj_as(List[Scenario], scenarios_data)
|
|
187
|
+
|
|
188
|
+
return scenarios
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def fetch_scenario_by_name(scenario_id: str) -> Scenario:
|
|
192
|
+
"""
|
|
193
|
+
Get the full JSON manifest of a specific scenario.
|
|
194
|
+
|
|
195
|
+
:param scenario_id: id of the scenario to fetch
|
|
196
|
+
|
|
197
|
+
:return: the JSON of the scenario
|
|
198
|
+
"""
|
|
199
|
+
result = authorize(_get)(f"/scenario/{scenario_id}")
|
|
200
|
+
|
|
201
|
+
if result.status_code != 200:
|
|
202
|
+
_handle_error(
|
|
203
|
+
result, f"Cannot retrieve scenario '{scenario_id}' from scenario API"
|
|
204
|
+
)
|
|
205
|
+
else:
|
|
206
|
+
scenario_data = result.json()
|
|
207
|
+
scenario = Scenario(**scenario_data)
|
|
208
|
+
|
|
209
|
+
return scenario
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def fetch_topologies() -> List[Topology]:
|
|
213
|
+
"""
|
|
214
|
+
List all available topologies
|
|
215
|
+
|
|
216
|
+
:return: the list of topologies
|
|
217
|
+
"""
|
|
218
|
+
result = authorize(_get)("/topology/")
|
|
219
|
+
|
|
220
|
+
if result.status_code != 200:
|
|
221
|
+
_handle_error(result, "Cannot retrieve topologies from scenario API")
|
|
222
|
+
else:
|
|
223
|
+
topologies_data = result.json()
|
|
224
|
+
topologies = parse_obj_as(List[Topology], topologies_data)
|
|
225
|
+
|
|
226
|
+
return topologies
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def fetch_topology_by_name(topology_name: str) -> Topology:
|
|
230
|
+
"""
|
|
231
|
+
Get the topology object given its name.
|
|
232
|
+
|
|
233
|
+
:param topology_name: name of the topology
|
|
234
|
+
|
|
235
|
+
:return: the topology object
|
|
236
|
+
"""
|
|
237
|
+
params = {"topology_name": topology_name}
|
|
238
|
+
result = authorize(_get)("/topology/info", params=params)
|
|
239
|
+
|
|
240
|
+
if result.status_code != 200:
|
|
241
|
+
_handle_error(
|
|
242
|
+
result, f"Cannot retrieve topology '{topology_name}' from scenario API"
|
|
243
|
+
)
|
|
244
|
+
else:
|
|
245
|
+
topology_data = result.json()
|
|
246
|
+
topology = Topology(**topology_data)
|
|
247
|
+
|
|
248
|
+
return topology
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def fetch_baseboxes() -> List[Basebox]:
|
|
252
|
+
"""
|
|
253
|
+
List all available baseboxes
|
|
254
|
+
|
|
255
|
+
:return: the list of baseboxes
|
|
256
|
+
"""
|
|
257
|
+
result = authorize(_get)("/basebox/")
|
|
258
|
+
|
|
259
|
+
if result.status_code != 200:
|
|
260
|
+
_handle_error(result, "Cannot retrieve baseboxes from scenario API")
|
|
261
|
+
else:
|
|
262
|
+
baseboxes_data = result.json()
|
|
263
|
+
baseboxes = parse_obj_as(List[Basebox], baseboxes_data)
|
|
264
|
+
|
|
265
|
+
return baseboxes
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def fetch_basebox_by_id(basebox_id: str) -> Basebox:
|
|
269
|
+
"""
|
|
270
|
+
Get the basebox object given its ID.
|
|
271
|
+
|
|
272
|
+
:param basebox_id: name of the basebox
|
|
273
|
+
|
|
274
|
+
:return: the basebox object
|
|
275
|
+
"""
|
|
276
|
+
result = authorize(_get)(f"/basebox/{basebox_id}")
|
|
277
|
+
|
|
278
|
+
if result.status_code != 200:
|
|
279
|
+
_handle_error(
|
|
280
|
+
result, f"Cannot retrieve basebox '{basebox_id}' from scenario API"
|
|
281
|
+
)
|
|
282
|
+
else:
|
|
283
|
+
basebox_data = result.json()
|
|
284
|
+
basebox = Basebox(**basebox_data)
|
|
285
|
+
|
|
286
|
+
return basebox
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def get_log_collectors() -> List[LogCollector]:
|
|
290
|
+
"""
|
|
291
|
+
Retrieve log collectors configuration information.
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
result = authorize(_get)("/log_collector")
|
|
295
|
+
|
|
296
|
+
if result.status_code != 200:
|
|
297
|
+
_handle_error(
|
|
298
|
+
result,
|
|
299
|
+
"Cannot get log collectors data from scenario API",
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
return result.json()
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def create_lab_topology(
|
|
306
|
+
topology: Topology,
|
|
307
|
+
lab_config: LabConfig,
|
|
308
|
+
workspace_id: str,
|
|
309
|
+
public: bool,
|
|
310
|
+
) -> str:
|
|
311
|
+
"""
|
|
312
|
+
Create a lab with the given topology.
|
|
313
|
+
|
|
314
|
+
:param topology: the topology to run
|
|
315
|
+
|
|
316
|
+
:return: a lab uuid
|
|
317
|
+
"""
|
|
318
|
+
|
|
319
|
+
# Inject content information into lab_config
|
|
320
|
+
lab_config.content_type = ContentType.TOPOLOGY
|
|
321
|
+
lab_config.content_name = topology.name
|
|
322
|
+
|
|
323
|
+
# Convert model into dict
|
|
324
|
+
lab_config_dict = lab_config.model_dump(mode="json")
|
|
325
|
+
|
|
326
|
+
# API call
|
|
327
|
+
body = {"lab_config": lab_config_dict}
|
|
328
|
+
if public:
|
|
329
|
+
oidc_client = get_oidc_client()
|
|
330
|
+
subject_tokens = oidc_client.get_active_tokens()
|
|
331
|
+
body["public_access_config"] = {
|
|
332
|
+
"access_token": subject_tokens["access_token"],
|
|
333
|
+
"refresh_token": subject_tokens["refresh_token"],
|
|
334
|
+
}
|
|
335
|
+
result = authorize(_post)(
|
|
336
|
+
"/topology/create_lab",
|
|
337
|
+
params={"workspace_id": workspace_id},
|
|
338
|
+
json=body,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
if result.status_code != 200:
|
|
342
|
+
_handle_error(
|
|
343
|
+
result, f"Cannot run topology '{topology.name}' from scenario API"
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
return result.json()
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def run_lab_topology(
|
|
350
|
+
topology: Topology,
|
|
351
|
+
lab_config: LabConfig,
|
|
352
|
+
workspace_id: str,
|
|
353
|
+
public: bool,
|
|
354
|
+
) -> str:
|
|
355
|
+
"""
|
|
356
|
+
Run the given topology.
|
|
357
|
+
|
|
358
|
+
:param topology: the topology to run
|
|
359
|
+
|
|
360
|
+
:return: a lab id
|
|
361
|
+
"""
|
|
362
|
+
|
|
363
|
+
# Inject content information into lab_config
|
|
364
|
+
lab_config.content_type = ContentType.TOPOLOGY
|
|
365
|
+
lab_config.content_name = topology.name
|
|
366
|
+
|
|
367
|
+
# Convert model into dict
|
|
368
|
+
lab_config_dict = lab_config.model_dump(mode="json")
|
|
369
|
+
|
|
370
|
+
# API call
|
|
371
|
+
body = {"lab_config": lab_config_dict}
|
|
372
|
+
if public:
|
|
373
|
+
oidc_client = get_oidc_client()
|
|
374
|
+
subject_tokens = oidc_client.get_active_tokens()
|
|
375
|
+
body["public_access_config"] = {
|
|
376
|
+
"access_token": subject_tokens["access_token"],
|
|
377
|
+
"refresh_token": subject_tokens["refresh_token"],
|
|
378
|
+
}
|
|
379
|
+
result = authorize(_post)(
|
|
380
|
+
"/topology/run_lab",
|
|
381
|
+
params={"workspace_id": workspace_id},
|
|
382
|
+
json=body,
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
if result.status_code != 200:
|
|
386
|
+
_handle_error(
|
|
387
|
+
result, f"Cannot run topology '{topology.name}' from scenario API"
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
return result.json()
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def create_lab_basebox(
|
|
394
|
+
basebox_id: str,
|
|
395
|
+
lab_config: LabConfig,
|
|
396
|
+
workspace_id: str,
|
|
397
|
+
public: bool,
|
|
398
|
+
) -> str:
|
|
399
|
+
"""
|
|
400
|
+
Create a lab with the given basebox.
|
|
401
|
+
|
|
402
|
+
:param basebox_id: the basebox to run
|
|
403
|
+
|
|
404
|
+
:return: a lab uuid
|
|
405
|
+
"""
|
|
406
|
+
|
|
407
|
+
# Inject content information into lab_config
|
|
408
|
+
lab_config.content_type = ContentType.BASEBOX
|
|
409
|
+
lab_config.content_name = basebox_id
|
|
410
|
+
|
|
411
|
+
# Convert model into dict
|
|
412
|
+
lab_config_dict = lab_config.model_dump(mode="json")
|
|
413
|
+
|
|
414
|
+
# API call
|
|
415
|
+
body = {"lab_config": lab_config_dict}
|
|
416
|
+
if public:
|
|
417
|
+
oidc_client = get_oidc_client()
|
|
418
|
+
subject_tokens = oidc_client.get_active_tokens()
|
|
419
|
+
body["public_access_config"] = {
|
|
420
|
+
"access_token": subject_tokens["access_token"],
|
|
421
|
+
"refresh_token": subject_tokens["refresh_token"],
|
|
422
|
+
}
|
|
423
|
+
result = authorize(_post)(
|
|
424
|
+
"/basebox/create_lab",
|
|
425
|
+
params={"workspace_id": workspace_id},
|
|
426
|
+
json=body,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
if result.status_code != 200:
|
|
430
|
+
_handle_error(result, f"Cannot run basebox '{basebox_id}' from scenario API")
|
|
431
|
+
|
|
432
|
+
return result.json()
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def run_lab_basebox(
|
|
436
|
+
basebox_id: str,
|
|
437
|
+
lab_config: LabConfig,
|
|
438
|
+
workspace_id: str,
|
|
439
|
+
public: bool,
|
|
440
|
+
) -> str:
|
|
441
|
+
"""
|
|
442
|
+
Run the given basebox.
|
|
443
|
+
|
|
444
|
+
:param basebox_id: the basebox to run
|
|
445
|
+
|
|
446
|
+
:return: a lab id
|
|
447
|
+
"""
|
|
448
|
+
|
|
449
|
+
# Inject content information into lab_config
|
|
450
|
+
lab_config.content_type = ContentType.BASEBOX
|
|
451
|
+
lab_config.content_name = basebox_id
|
|
452
|
+
|
|
453
|
+
# Convert model into dict
|
|
454
|
+
lab_config_dict = lab_config.model_dump(mode="json")
|
|
455
|
+
|
|
456
|
+
# API call
|
|
457
|
+
body = {"lab_config": lab_config_dict}
|
|
458
|
+
if public:
|
|
459
|
+
oidc_client = get_oidc_client()
|
|
460
|
+
subject_tokens = oidc_client.get_active_tokens()
|
|
461
|
+
body["public_access_config"] = {
|
|
462
|
+
"access_token": subject_tokens["access_token"],
|
|
463
|
+
"refresh_token": subject_tokens["refresh_token"],
|
|
464
|
+
}
|
|
465
|
+
result = authorize(_post)(
|
|
466
|
+
"/basebox/run_lab",
|
|
467
|
+
params={"workspace_id": workspace_id},
|
|
468
|
+
json=body,
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
if result.status_code != 200:
|
|
472
|
+
_handle_error(result, f"Cannot run basebox '{basebox_id}' from scenario API")
|
|
473
|
+
|
|
474
|
+
return result.json()
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def create_lab_scenario(
|
|
478
|
+
scenario: Scenario,
|
|
479
|
+
scenario_profile: str,
|
|
480
|
+
lab_config: LabConfig,
|
|
481
|
+
workspace_id: str,
|
|
482
|
+
public: bool,
|
|
483
|
+
) -> str:
|
|
484
|
+
"""
|
|
485
|
+
Create a lab with the given scenario.
|
|
486
|
+
|
|
487
|
+
:param scenario: the scenario to run
|
|
488
|
+
|
|
489
|
+
:return: a lab uuid
|
|
490
|
+
"""
|
|
491
|
+
|
|
492
|
+
# Inject content information into lab_config
|
|
493
|
+
lab_config.content_type = ContentType.KILLCHAIN
|
|
494
|
+
lab_config.content_name = scenario.name
|
|
495
|
+
lab_config.scenario_profile = scenario_profile
|
|
496
|
+
|
|
497
|
+
# Convert model into dict
|
|
498
|
+
lab_config_dict = lab_config.model_dump(mode="json")
|
|
499
|
+
|
|
500
|
+
# API call
|
|
501
|
+
body = {"lab_config": lab_config_dict}
|
|
502
|
+
if public:
|
|
503
|
+
oidc_client = get_oidc_client()
|
|
504
|
+
subject_tokens = oidc_client.get_active_tokens()
|
|
505
|
+
body["public_access_config"] = {
|
|
506
|
+
"access_token": subject_tokens["access_token"],
|
|
507
|
+
"refresh_token": subject_tokens["refresh_token"],
|
|
508
|
+
}
|
|
509
|
+
result = authorize(_post)(
|
|
510
|
+
"/scenario/create_lab",
|
|
511
|
+
params={"workspace_id": workspace_id},
|
|
512
|
+
json=body,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
if result.status_code != 200:
|
|
516
|
+
_handle_error(
|
|
517
|
+
result, f"Cannot run scenario '{scenario.name}' from scenario API"
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
return result.json()
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def run_lab_scenario(
|
|
524
|
+
scenario: Scenario,
|
|
525
|
+
scenario_profile: str,
|
|
526
|
+
lab_config: LabConfig,
|
|
527
|
+
workspace_id: str,
|
|
528
|
+
public: bool,
|
|
529
|
+
) -> str:
|
|
530
|
+
"""
|
|
531
|
+
Run the given scenario.
|
|
532
|
+
|
|
533
|
+
:param scenario: the scenario to run
|
|
534
|
+
|
|
535
|
+
:return: a lab id
|
|
536
|
+
"""
|
|
537
|
+
|
|
538
|
+
# Inject content information into lab_config
|
|
539
|
+
lab_config.content_type = ContentType.KILLCHAIN
|
|
540
|
+
lab_config.content_name = scenario.name
|
|
541
|
+
lab_config.scenario_profile = scenario_profile
|
|
542
|
+
|
|
543
|
+
# Convert model into dict
|
|
544
|
+
lab_config_dict = lab_config.model_dump(mode="json")
|
|
545
|
+
|
|
546
|
+
# API call
|
|
547
|
+
body = {"lab_config": lab_config_dict}
|
|
548
|
+
if public:
|
|
549
|
+
oidc_client = get_oidc_client()
|
|
550
|
+
subject_tokens = oidc_client.get_active_tokens()
|
|
551
|
+
body["public_access_config"] = {
|
|
552
|
+
"access_token": subject_tokens["access_token"],
|
|
553
|
+
"refresh_token": subject_tokens["refresh_token"],
|
|
554
|
+
}
|
|
555
|
+
result = authorize(_post)(
|
|
556
|
+
"/scenario/run_lab",
|
|
557
|
+
params={"workspace_id": workspace_id},
|
|
558
|
+
json=body,
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
if result.status_code != 200:
|
|
562
|
+
_handle_error(
|
|
563
|
+
result, f"Cannot run scenario '{scenario.name}' from scenario API"
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
return result.json()
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def create_lab_bas(
|
|
570
|
+
lab_config: LabConfig,
|
|
571
|
+
workspace_id: str,
|
|
572
|
+
) -> str:
|
|
573
|
+
|
|
574
|
+
# Inject content information into lab_config
|
|
575
|
+
lab_config.content_type = ContentType.BAS
|
|
576
|
+
lab_config.content_name = "bas"
|
|
577
|
+
lab_config.scenario_profile = "bas"
|
|
578
|
+
|
|
579
|
+
# Convert model into dict
|
|
580
|
+
lab_config_dict = lab_config.model_dump(mode="json")
|
|
581
|
+
|
|
582
|
+
# API call
|
|
583
|
+
result = authorize(_post)(
|
|
584
|
+
"/bas/create_lab",
|
|
585
|
+
params={"workspace_id": workspace_id},
|
|
586
|
+
json={
|
|
587
|
+
"lab_config": lab_config_dict,
|
|
588
|
+
},
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
if result.status_code != 200:
|
|
592
|
+
_handle_error(result, "Cannot create BAS campaign from scenario API")
|
|
593
|
+
|
|
594
|
+
return result.json()
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def run_lab_bas(
|
|
598
|
+
lab_config: LabConfig,
|
|
599
|
+
workspace_id: str,
|
|
600
|
+
) -> str:
|
|
601
|
+
|
|
602
|
+
# Inject content information into lab_config
|
|
603
|
+
lab_config.content_type = ContentType.BAS
|
|
604
|
+
lab_config.content_name = "bas"
|
|
605
|
+
lab_config.scenario_profile = "bas"
|
|
606
|
+
|
|
607
|
+
# Convert model into dict
|
|
608
|
+
lab_config_dict = lab_config.model_dump(mode="json")
|
|
609
|
+
|
|
610
|
+
# API call
|
|
611
|
+
result = authorize(_post)(
|
|
612
|
+
"/bas/run_lab",
|
|
613
|
+
params={"workspace_id": workspace_id},
|
|
614
|
+
json={
|
|
615
|
+
"lab_config": lab_config_dict,
|
|
616
|
+
},
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
if result.status_code != 200:
|
|
620
|
+
_handle_error(result, "Cannot run BAS campaign from scenario API")
|
|
621
|
+
|
|
622
|
+
return result.json()
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def create_lab_attack(
|
|
626
|
+
attack: UnitAttack,
|
|
627
|
+
scenario_profile: str,
|
|
628
|
+
lab_config: LabConfig,
|
|
629
|
+
workspace_id: str,
|
|
630
|
+
public: bool,
|
|
631
|
+
) -> str:
|
|
632
|
+
"""
|
|
633
|
+
Create a lab with the given attack.
|
|
634
|
+
|
|
635
|
+
:param attack: the attack to run
|
|
636
|
+
:param scenario_profile: the unit scenario to run for the current attack
|
|
637
|
+
|
|
638
|
+
:return: a lab uuid
|
|
639
|
+
"""
|
|
640
|
+
|
|
641
|
+
# Inject content information into lab_config
|
|
642
|
+
lab_config.content_type = ContentType.ATTACK
|
|
643
|
+
lab_config.content_name = attack.name
|
|
644
|
+
lab_config.scenario_profile = scenario_profile
|
|
645
|
+
|
|
646
|
+
# Convert model into dict
|
|
647
|
+
lab_config_dict = lab_config.model_dump(mode="json")
|
|
648
|
+
|
|
649
|
+
# API call
|
|
650
|
+
body = {"lab_config": lab_config_dict}
|
|
651
|
+
if public:
|
|
652
|
+
oidc_client = get_oidc_client()
|
|
653
|
+
subject_tokens = oidc_client.get_active_tokens()
|
|
654
|
+
body["public_access_config"] = {
|
|
655
|
+
"access_token": subject_tokens["access_token"],
|
|
656
|
+
"refresh_token": subject_tokens["refresh_token"],
|
|
657
|
+
}
|
|
658
|
+
result = authorize(_post)(
|
|
659
|
+
"/unit_attack/create_lab",
|
|
660
|
+
params={"workspace_id": workspace_id},
|
|
661
|
+
json=body,
|
|
662
|
+
)
|
|
663
|
+
|
|
664
|
+
if result.status_code != 200:
|
|
665
|
+
_handle_error(result, f"Cannot run attack '{attack.name}' from scenario API")
|
|
666
|
+
|
|
667
|
+
return result.json()
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def run_lab_attack(
|
|
671
|
+
attack: UnitAttack,
|
|
672
|
+
scenario_profile: str,
|
|
673
|
+
lab_config: LabConfig,
|
|
674
|
+
workspace_id: str,
|
|
675
|
+
public: bool,
|
|
676
|
+
) -> str:
|
|
677
|
+
"""
|
|
678
|
+
Run the given attack.
|
|
679
|
+
|
|
680
|
+
:param attack: the attack to run
|
|
681
|
+
:param scenario_profile: the unit scenario to run for the current attack
|
|
682
|
+
|
|
683
|
+
:return: a lab id
|
|
684
|
+
"""
|
|
685
|
+
|
|
686
|
+
# Inject content information into lab_config
|
|
687
|
+
lab_config.content_type = ContentType.ATTACK
|
|
688
|
+
lab_config.content_name = attack.name
|
|
689
|
+
lab_config.scenario_profile = scenario_profile
|
|
690
|
+
|
|
691
|
+
# Convert model into dict
|
|
692
|
+
lab_config_dict = lab_config.model_dump(mode="json")
|
|
693
|
+
|
|
694
|
+
# API call
|
|
695
|
+
body = {"lab_config": lab_config_dict}
|
|
696
|
+
if public:
|
|
697
|
+
oidc_client = get_oidc_client()
|
|
698
|
+
subject_tokens = oidc_client.get_active_tokens()
|
|
699
|
+
body["public_access_config"] = {
|
|
700
|
+
"access_token": subject_tokens["access_token"],
|
|
701
|
+
"refresh_token": subject_tokens["refresh_token"],
|
|
702
|
+
}
|
|
703
|
+
result = authorize(_post)(
|
|
704
|
+
"/unit_attack/run_lab",
|
|
705
|
+
params={"workspace_id": workspace_id},
|
|
706
|
+
json=body,
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
if result.status_code != 200:
|
|
710
|
+
_handle_error(result, f"Cannot run attack '{attack.name}' from scenario API")
|
|
711
|
+
|
|
712
|
+
return result.json()
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def fetch_labs(
|
|
716
|
+
all_labs: bool = False, pagination: Pagination = Pagination()
|
|
717
|
+
) -> LabListReply:
|
|
718
|
+
"""
|
|
719
|
+
Get list of current labs.
|
|
720
|
+
If all_labs is True, retrieve also stopped labs.
|
|
721
|
+
|
|
722
|
+
:return: a list containg the current labs.
|
|
723
|
+
"""
|
|
724
|
+
params = {"all_labs": all_labs, "limit": pagination.limit, "page": pagination.page}
|
|
725
|
+
result = authorize(_get)("/runner/", params=params)
|
|
726
|
+
|
|
727
|
+
if result.status_code != 200:
|
|
728
|
+
_handle_error(result, "Cannot get lab list from scenario API")
|
|
729
|
+
|
|
730
|
+
return result.json()
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
def fetch_lab(lab_id: str) -> Dict:
|
|
734
|
+
"""
|
|
735
|
+
Get status of a lab.
|
|
736
|
+
|
|
737
|
+
:param lab_id: the lab id
|
|
738
|
+
|
|
739
|
+
:return: a dict containg the lab status and potential results
|
|
740
|
+
"""
|
|
741
|
+
result = authorize(_get)(f"/runner/{lab_id}")
|
|
742
|
+
|
|
743
|
+
if result.status_code != 200:
|
|
744
|
+
_handle_error(
|
|
745
|
+
result, f"Cannot get lab status from id '{lab_id}' from scenario API"
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
return result.json()
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
def run_lab(lab_id: str) -> None:
|
|
752
|
+
"""
|
|
753
|
+
Run a lab.
|
|
754
|
+
|
|
755
|
+
:param lab_id: the lab id
|
|
756
|
+
"""
|
|
757
|
+
result = authorize(_get)(f"/runner/{lab_id}/run")
|
|
758
|
+
|
|
759
|
+
if result.status_code != 200:
|
|
760
|
+
_handle_error(
|
|
761
|
+
result,
|
|
762
|
+
f"Cannot run lab of id '{lab_id}' from scenario API",
|
|
763
|
+
)
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
def stop_lab(lab_id: str) -> None:
|
|
767
|
+
"""
|
|
768
|
+
Stop lab execution.
|
|
769
|
+
|
|
770
|
+
:param lab_id: the lab id
|
|
771
|
+
|
|
772
|
+
:return: a dict containg the lab status and potential results
|
|
773
|
+
"""
|
|
774
|
+
result = authorize(_get)(f"/runner/{lab_id}/stop")
|
|
775
|
+
|
|
776
|
+
if result.status_code != 200:
|
|
777
|
+
_handle_error(
|
|
778
|
+
result,
|
|
779
|
+
f"Cannot stop lab execution of id '{lab_id}' from scenario API",
|
|
780
|
+
)
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
def resume_lab(lab_id: str) -> None:
|
|
784
|
+
"""
|
|
785
|
+
Resume lab execution.
|
|
786
|
+
|
|
787
|
+
:param lab_id: the lab id
|
|
788
|
+
|
|
789
|
+
:return: a dict containg the lab status and potential results
|
|
790
|
+
"""
|
|
791
|
+
result = authorize(_get)(f"/runner/{lab_id}/resume")
|
|
792
|
+
|
|
793
|
+
if result.status_code != 200:
|
|
794
|
+
_handle_error(
|
|
795
|
+
result,
|
|
796
|
+
f"Cannot resume lab execution of id '{lab_id}' from scenario API",
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
|
|
800
|
+
def fetch_lab_paused_status(lab_id: str) -> PausedStatus:
|
|
801
|
+
"""
|
|
802
|
+
Fetch lab status during pause.
|
|
803
|
+
|
|
804
|
+
:param lab_id: the lab id
|
|
805
|
+
|
|
806
|
+
:return: a dict containg the lab paused status
|
|
807
|
+
"""
|
|
808
|
+
result = authorize(_get)(f"/runner/{lab_id}/paused_status")
|
|
809
|
+
|
|
810
|
+
if result.status_code != 200:
|
|
811
|
+
_handle_error(
|
|
812
|
+
result,
|
|
813
|
+
f"Cannot resume lab execution of id '{lab_id}' from scenario API",
|
|
814
|
+
)
|
|
815
|
+
else:
|
|
816
|
+
paused_status_data = result.json()
|
|
817
|
+
paused_status = PausedStatus(**paused_status_data)
|
|
818
|
+
|
|
819
|
+
return paused_status
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
def fetch_lab_topology(lab_id: str) -> Topology:
|
|
823
|
+
"""
|
|
824
|
+
Get scenario topology of a current lab.
|
|
825
|
+
|
|
826
|
+
:param lab_id: the lab id
|
|
827
|
+
|
|
828
|
+
:return: a dict containg the topology
|
|
829
|
+
"""
|
|
830
|
+
result = authorize(_get)(f"/runner/{lab_id}/topology")
|
|
831
|
+
|
|
832
|
+
if result.status_code != 200:
|
|
833
|
+
_handle_error(
|
|
834
|
+
result,
|
|
835
|
+
f"Cannot get scenario topology from id '{lab_id}' from scenario API",
|
|
836
|
+
)
|
|
837
|
+
else:
|
|
838
|
+
topology_data = result.json()
|
|
839
|
+
topology = Topology(**topology_data)
|
|
840
|
+
|
|
841
|
+
return topology
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
def fetch_lab_nodes(lab_id: str) -> List:
|
|
845
|
+
"""
|
|
846
|
+
Get scenario nodes of a current lab.
|
|
847
|
+
|
|
848
|
+
:param lab_id: the lab id
|
|
849
|
+
|
|
850
|
+
:return: the current nodes
|
|
851
|
+
"""
|
|
852
|
+
result = authorize(_get)(f"/runner/{lab_id}/nodes")
|
|
853
|
+
|
|
854
|
+
if result.status_code != 200:
|
|
855
|
+
_handle_error(
|
|
856
|
+
result, f"Cannot get scenario nodes from id '{lab_id}' from scenario API"
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
return result.json()
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+
def fetch_lab_assets(lab_id: str) -> List:
|
|
863
|
+
"""
|
|
864
|
+
Get scenario assets of a current lab.
|
|
865
|
+
|
|
866
|
+
:param lab_id: the lab id
|
|
867
|
+
|
|
868
|
+
:return: the current assets
|
|
869
|
+
"""
|
|
870
|
+
result = authorize(_get)(f"/runner/{lab_id}/assets")
|
|
871
|
+
|
|
872
|
+
if result.status_code != 200:
|
|
873
|
+
_handle_error(
|
|
874
|
+
result,
|
|
875
|
+
f"Cannot get scenario assets from id '{lab_id}' from scenario API",
|
|
876
|
+
)
|
|
877
|
+
|
|
878
|
+
return result.json()
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
def fetch_lab_attack_report(lab_id: str) -> List:
|
|
882
|
+
"""
|
|
883
|
+
Get scenario attack report of a current lab.
|
|
884
|
+
|
|
885
|
+
:param lab_id: the lab id
|
|
886
|
+
|
|
887
|
+
:return: the current attack report
|
|
888
|
+
"""
|
|
889
|
+
result = authorize(_get)(f"/runner/{lab_id}/attack_report")
|
|
890
|
+
|
|
891
|
+
if result.status_code != 200:
|
|
892
|
+
_handle_error(
|
|
893
|
+
result,
|
|
894
|
+
f"Cannot get scenario attack report from id '{lab_id}' from scenario API",
|
|
895
|
+
)
|
|
896
|
+
|
|
897
|
+
return result.json()
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
def fetch_lab_attack_infras(lab_id: str) -> List:
|
|
901
|
+
"""
|
|
902
|
+
Get scenario attack infrastructures of a current lab.
|
|
903
|
+
|
|
904
|
+
:param lab_id: the lab id
|
|
905
|
+
|
|
906
|
+
:return: the current attack infrastructures
|
|
907
|
+
"""
|
|
908
|
+
result = authorize(_get)(f"/runner/{lab_id}/attack_infras")
|
|
909
|
+
|
|
910
|
+
if result.status_code != 200:
|
|
911
|
+
_handle_error(
|
|
912
|
+
result,
|
|
913
|
+
f"Cannot get scenario attack infras from id '{lab_id}' from scenario API",
|
|
914
|
+
)
|
|
915
|
+
|
|
916
|
+
return result.json()
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
def fetch_lab_attack_sessions(lab_id: str) -> List:
|
|
920
|
+
"""
|
|
921
|
+
Get scenario attack sessions of a current lab.
|
|
922
|
+
|
|
923
|
+
:param lab_id: the lab id
|
|
924
|
+
|
|
925
|
+
:return: the current attack sessions
|
|
926
|
+
"""
|
|
927
|
+
result = authorize(_get)(f"/runner/{lab_id}/attack_sessions")
|
|
928
|
+
|
|
929
|
+
if result.status_code != 200:
|
|
930
|
+
_handle_error(
|
|
931
|
+
result,
|
|
932
|
+
f"Cannot get scenario attack sessions from id '{lab_id}' from scenario API",
|
|
933
|
+
)
|
|
934
|
+
|
|
935
|
+
return result.json()
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
def fetch_lab_attack_knowledge(lab_id: str) -> Dict:
|
|
939
|
+
"""
|
|
940
|
+
Get scenario attack knowledge of a current lab.
|
|
941
|
+
|
|
942
|
+
:param lab_id: the lab id
|
|
943
|
+
|
|
944
|
+
:return: the attack knowledge
|
|
945
|
+
"""
|
|
946
|
+
result = authorize(_get)(f"/runner/{lab_id}/attack_knowledge")
|
|
947
|
+
|
|
948
|
+
if result.status_code != 200:
|
|
949
|
+
_handle_error(
|
|
950
|
+
result,
|
|
951
|
+
f"Cannot get scenario attack knowledge from id '{lab_id}' from scenario API",
|
|
952
|
+
)
|
|
953
|
+
|
|
954
|
+
return result.json()
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
def fetch_lab_notifications(lab_id: str) -> List:
|
|
958
|
+
"""
|
|
959
|
+
Get scenario notifications of a current lab.
|
|
960
|
+
|
|
961
|
+
:param lab_id: the lab id
|
|
962
|
+
|
|
963
|
+
:return: the notifications as a list of strings
|
|
964
|
+
"""
|
|
965
|
+
result = authorize(_get)(f"/runner/{lab_id}/notifications")
|
|
966
|
+
|
|
967
|
+
if result.status_code != 200:
|
|
968
|
+
_handle_error(
|
|
969
|
+
result,
|
|
970
|
+
f"Cannot get scenario notifications from id '{lab_id}' from scenario API",
|
|
971
|
+
)
|
|
972
|
+
|
|
973
|
+
return result.json()
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
def delete_lab(lab_id: str) -> None:
|
|
977
|
+
"""
|
|
978
|
+
Delete a lab from its ID.
|
|
979
|
+
|
|
980
|
+
:param lab_id: the lab id
|
|
981
|
+
"""
|
|
982
|
+
result = authorize(_delete)(f"/runner/{lab_id}")
|
|
983
|
+
|
|
984
|
+
if result.status_code != 200:
|
|
985
|
+
_handle_error(
|
|
986
|
+
result,
|
|
987
|
+
f"Cannot delete lab from id '{lab_id}' from scenario API",
|
|
988
|
+
)
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
def fetch_lab_config(lab_id: str) -> LabConfig:
|
|
992
|
+
"""
|
|
993
|
+
Get lab config of a current lab.
|
|
994
|
+
|
|
995
|
+
:param lab_id: the lab id
|
|
996
|
+
|
|
997
|
+
:return: the lab config
|
|
998
|
+
"""
|
|
999
|
+
result = authorize(_get)(f"/runner/{lab_id}/lab_config")
|
|
1000
|
+
|
|
1001
|
+
if result.status_code != 200:
|
|
1002
|
+
_handle_error(
|
|
1003
|
+
result,
|
|
1004
|
+
f"Cannot get lab config from id '{lab_id}' from scenario API",
|
|
1005
|
+
)
|
|
1006
|
+
else:
|
|
1007
|
+
lab_config_data = result.json()
|
|
1008
|
+
lab_config = LabConfig(**lab_config_data)
|
|
1009
|
+
|
|
1010
|
+
return lab_config
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
def fetch_lab_remote_access(lab_id: str) -> RemoteAccess:
|
|
1014
|
+
"""
|
|
1015
|
+
Get info to remotly access lab VMs.
|
|
1016
|
+
|
|
1017
|
+
:param lab_id: the lab id
|
|
1018
|
+
|
|
1019
|
+
:return: the remote lab VMs info
|
|
1020
|
+
"""
|
|
1021
|
+
result = authorize(_get)(f"/runner/{lab_id}/remote_access")
|
|
1022
|
+
|
|
1023
|
+
if result.status_code != 200:
|
|
1024
|
+
_handle_error(
|
|
1025
|
+
result,
|
|
1026
|
+
f"Cannot get lab config from id '{lab_id}' from scenario API",
|
|
1027
|
+
)
|
|
1028
|
+
else:
|
|
1029
|
+
remote_access_data = result.json()
|
|
1030
|
+
remote_access = RemoteAccess(**remote_access_data)
|
|
1031
|
+
|
|
1032
|
+
return remote_access
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
def fetch_lab_security_alerts(lab_id: str) -> Dict[str, list[SecurityAlert]]:
|
|
1036
|
+
"""
|
|
1037
|
+
Fetch all security alerts, per security products.
|
|
1038
|
+
|
|
1039
|
+
:return: the dict of security alerts
|
|
1040
|
+
"""
|
|
1041
|
+
result = authorize(_get)(f"/runner/{lab_id}/security_alerts")
|
|
1042
|
+
|
|
1043
|
+
if result.status_code != 200:
|
|
1044
|
+
_handle_error(result, "Cannot retrieve security alerts from scenario API")
|
|
1045
|
+
else:
|
|
1046
|
+
security_alerts_data = result.json()
|
|
1047
|
+
security_alerts = parse_obj_as(
|
|
1048
|
+
dict[str, list[SecurityAlert]], security_alerts_data
|
|
1049
|
+
)
|
|
1050
|
+
|
|
1051
|
+
return security_alerts
|
|
1052
|
+
|
|
1053
|
+
|
|
1054
|
+
def fetch_signatures() -> Dict[str, Signature]:
|
|
1055
|
+
"""
|
|
1056
|
+
List all available signatures
|
|
1057
|
+
|
|
1058
|
+
:return: the list of signature
|
|
1059
|
+
"""
|
|
1060
|
+
result = authorize(_get)("/signature/")
|
|
1061
|
+
|
|
1062
|
+
if result.status_code != 200:
|
|
1063
|
+
_handle_error(result, "Cannot retrieve signatures from scenario API")
|
|
1064
|
+
else:
|
|
1065
|
+
signatures_data = result.json()
|
|
1066
|
+
signatures = parse_obj_as(Dict[str, Signature], signatures_data)
|
|
1067
|
+
|
|
1068
|
+
return signatures
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
def raise_for_errors(result):
|
|
1072
|
+
if result.status_code != 200:
|
|
1073
|
+
_handle_error(result, "Cannot retrieve signatures from scenario API")
|
|
1074
|
+
|
|
1075
|
+
|
|
1076
|
+
def parse_dict_to_signature(signature_data) -> Signature:
|
|
1077
|
+
signature = None
|
|
1078
|
+
|
|
1079
|
+
signature = Signature(**signature_data)
|
|
1080
|
+
|
|
1081
|
+
return signature
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
def fetch_signatures_by_scenario_id(scenario_id: str) -> List[Signature]:
|
|
1085
|
+
sig_list: List[Signature] = []
|
|
1086
|
+
params = {"scenario_id": scenario_id}
|
|
1087
|
+
result = authorize(_get)("/signature/scenario/info", params=params)
|
|
1088
|
+
raise_for_errors(result)
|
|
1089
|
+
signatures = result.json()
|
|
1090
|
+
for sig in signatures:
|
|
1091
|
+
signature = parse_dict_to_signature(sig)
|
|
1092
|
+
sig_list.append(signature)
|
|
1093
|
+
|
|
1094
|
+
return sig_list
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
def fetch_signature_by_attack_id(signature_id: str) -> Signature:
|
|
1098
|
+
"""
|
|
1099
|
+
Get the signature object given its attack reference id.
|
|
1100
|
+
|
|
1101
|
+
:param signature_id: name of the signature
|
|
1102
|
+
|
|
1103
|
+
:return: the signature object
|
|
1104
|
+
"""
|
|
1105
|
+
params = {"signature_id": signature_id}
|
|
1106
|
+
result = authorize(_get)("/signature/attack/info", params=params)
|
|
1107
|
+
raise_for_errors(result)
|
|
1108
|
+
signature = parse_dict_to_signature(result.json())
|
|
1109
|
+
|
|
1110
|
+
return signature
|
|
1111
|
+
|
|
1112
|
+
|
|
1113
|
+
def fetch_signature_by_signature_id(signature_id: str) -> Signature:
|
|
1114
|
+
"""
|
|
1115
|
+
Get the signature object given its id.
|
|
1116
|
+
|
|
1117
|
+
:param signature_id: id of the signature
|
|
1118
|
+
|
|
1119
|
+
:return: the signature object
|
|
1120
|
+
"""
|
|
1121
|
+
params = {"signature_id": signature_id}
|
|
1122
|
+
result = authorize(_get)("/signature/info", params=params)
|
|
1123
|
+
raise_for_errors(result)
|
|
1124
|
+
signature = parse_dict_to_signature(result.json())
|
|
1125
|
+
|
|
1126
|
+
return signature
|
|
1127
|
+
|
|
1128
|
+
|
|
1129
|
+
def fetch_agents(organization_id: str, bas_id: str = "") -> List:
|
|
1130
|
+
"""
|
|
1131
|
+
Get scenario notifications of a current lab.
|
|
1132
|
+
|
|
1133
|
+
:param lab_id: the lab id
|
|
1134
|
+
|
|
1135
|
+
:return: the notifications as a list of strings
|
|
1136
|
+
"""
|
|
1137
|
+
url = f"/agents/?organization_id={organization_id}"
|
|
1138
|
+
if bas_id != "":
|
|
1139
|
+
url = url + f"&bas_id={bas_id}"
|
|
1140
|
+
result = authorize(_get)(url)
|
|
1141
|
+
|
|
1142
|
+
if result.status_code != 200:
|
|
1143
|
+
_handle_error(
|
|
1144
|
+
result,
|
|
1145
|
+
"Cannot get agents from scenario API",
|
|
1146
|
+
)
|
|
1147
|
+
|
|
1148
|
+
return result.json()
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
def fetch_beacons(agent_id: str) -> List:
|
|
1152
|
+
"""
|
|
1153
|
+
Get scenario notifications of a current lab.
|
|
1154
|
+
|
|
1155
|
+
:param lab_id: the lab id
|
|
1156
|
+
|
|
1157
|
+
:return: the notifications as a list of strings
|
|
1158
|
+
"""
|
|
1159
|
+
url = f"/agents/{agent_id}/beacons"
|
|
1160
|
+
result = authorize(_get)(url)
|
|
1161
|
+
|
|
1162
|
+
if result.status_code != 200:
|
|
1163
|
+
_handle_error(
|
|
1164
|
+
result,
|
|
1165
|
+
"Cannot get beacons agents from scenario API",
|
|
1166
|
+
)
|
|
1167
|
+
|
|
1168
|
+
return result.json()
|
|
1169
|
+
|
|
1170
|
+
|
|
1171
|
+
def add_order_agents(
|
|
1172
|
+
agent_id: str, action_type: str, content: str, organization_id: str
|
|
1173
|
+
) -> List:
|
|
1174
|
+
result = authorize(_post)(
|
|
1175
|
+
f"/agents/{agent_id}/orders",
|
|
1176
|
+
params={"organization_id": organization_id},
|
|
1177
|
+
json={"action_type": action_type, "content": content},
|
|
1178
|
+
)
|
|
1179
|
+
|
|
1180
|
+
if result.status_code != 200:
|
|
1181
|
+
_handle_error(result, f"Cannot add order to agent {agent_id} from scenario API")
|
|
1182
|
+
|
|
1183
|
+
return result.json()
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
def delete_agents(agent_id: str, organization_id: str) -> List:
|
|
1187
|
+
result = authorize(_delete)(
|
|
1188
|
+
f"/agents/{agent_id}",
|
|
1189
|
+
params={"organization_id": organization_id},
|
|
1190
|
+
json={},
|
|
1191
|
+
)
|
|
1192
|
+
|
|
1193
|
+
if result.status_code != 200:
|
|
1194
|
+
_handle_error(result, f"Cannot add order to agent {agent_id} from scenario API")
|
|
1195
|
+
|
|
1196
|
+
return result.json()
|