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.
@@ -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()