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,317 @@
1
+ # -*- coding: utf-8 -*-
2
+ import argparse
3
+ import json
4
+ import sys
5
+ import traceback
6
+ from typing import Any
7
+
8
+ from mantis_scenario_model.common import Empty
9
+ from mantis_scenario_model.lab_config_model import LabConfig
10
+ from pydantic.json import pydantic_encoder
11
+ from ruamel.yaml import YAML
12
+
13
+ from mantis_api_client import scenario_api
14
+ from mantis_api_client.oidc import get_oidc_client
15
+ from mantis_api_client.utils import colored
16
+ from mantis_api_client.utils import wait_lab
17
+
18
+
19
+ #
20
+ # 'attack_list_handler' handler
21
+ #
22
+ def attack_list_handler(args: Any) -> None:
23
+ try:
24
+ attacks = sorted(scenario_api.fetch_attacks(), key=lambda x: x.name)
25
+ except Exception as e:
26
+ print(colored(f"Error when fetching attacks: '{e}'", "red"))
27
+ sys.exit(1)
28
+
29
+ if args.json:
30
+ print(json.dumps(attacks, default=pydantic_encoder))
31
+ return
32
+
33
+ width = 35
34
+ print(f"[+] Available attacks ({len(attacks)}):")
35
+ name = "NAME"
36
+ print(f" [+] \033[1m{name: <{width}}- DESCRIPTION\033[0m")
37
+
38
+ for attack in attacks:
39
+ if not isinstance(attack.mitre_data.subtechnique, Empty):
40
+ mitre_print = attack.mitre_data.subtechnique.id
41
+ else:
42
+ mitre_print = attack.mitre_data.technique.id
43
+
44
+ print(" [+] ", end="")
45
+ print(f"{attack.name: <{width}}", end="")
46
+ print(f"- {attack.title} ({mitre_print})")
47
+
48
+
49
+ #
50
+ # 'attack_info_handler' handler
51
+ #
52
+ def attack_info_handler(args: Any) -> None:
53
+ # Parameters
54
+ attack_name = args.attack_name
55
+
56
+ try:
57
+ attack = scenario_api.fetch_attack_by_name(attack_name)
58
+ except Exception as e:
59
+ print(
60
+ colored(
61
+ f"Error when fetching attack {attack_name}: '{e}'", "red", "on_white"
62
+ )
63
+ )
64
+ sys.exit(1)
65
+
66
+ if args.json:
67
+ print(attack.json())
68
+ return
69
+
70
+ print("[+] Attack information:")
71
+ print(f" [+] \033[1mID\033[0m: {attack.worker_id}")
72
+ print(f" [+] \033[1mName\033[0m: {attack.name}")
73
+ print(f" [+] \033[1mDescription\033[0m: {attack.description}")
74
+ print(" [+] \033[1mAvailable scenario profiles\033[0m:")
75
+ for scenario_profile in attack.scenario_profiles:
76
+ print(
77
+ f" [+] {scenario_profile.name} (Topology: {scenario_profile.topology_name})"
78
+ )
79
+
80
+ if attack.options:
81
+ print(" [+] \033[1mAvailable attack options\033[0m:")
82
+ for option in attack.options:
83
+ default = ""
84
+ if option.default:
85
+ default = f"(\033[1mdefault\033[0m: {option.default})"
86
+ print(f" [+] \033[1m{option.name}\033[0m: {option.description} {default}")
87
+
88
+
89
+ def _attack_create_or_run_lab(args: Any, do_run: bool = True):
90
+ # Parameters
91
+ attack_name = args.attack_name
92
+ scenario_profile = args.scenario_profile
93
+ lab_config_path = args.lab_config_path
94
+
95
+ if not args.workspace_id:
96
+ try:
97
+ workspace_id = get_oidc_client().get_default_workspace(raise_exc=True)
98
+ except Exception as e:
99
+ print(colored(f"Error when fetching attacks: '{e}'", "red"))
100
+ sys.exit(1)
101
+ else:
102
+ workspace_id = args.workspace_id
103
+
104
+ # Retrieve associated group id
105
+ if workspace_id is None:
106
+ print(colored("You have to specify a workspace with --workspace", "yellow"))
107
+ sys.exit(1)
108
+
109
+ # Safety checks
110
+ try:
111
+ attack = scenario_api.fetch_attack_by_name(attack_name)
112
+ except Exception as e:
113
+ print(colored(f"{e}", "red", "on_white"))
114
+ sys.exit(1)
115
+ print(f"[+] Going to create lab for: {attack.name}")
116
+
117
+ # Safety checks
118
+ if len(attack.scenario_profiles) == 0:
119
+ print(
120
+ f"Cannot run attack '{attack_name}', because it does not have any unit scenario"
121
+ )
122
+ sys.exit(-1)
123
+
124
+ if scenario_profile is None:
125
+ print(
126
+ "Needed argument --scenario_profile in order to choose the unit scenario to run"
127
+ )
128
+ print("Available unit scenarios:")
129
+ for available_scenario_profile in attack.scenario_profiles:
130
+ print(f" [+] {available_scenario_profile.name}")
131
+ sys.exit(-1)
132
+
133
+ for available_scenario_profile in attack.scenario_profiles:
134
+ if available_scenario_profile.name == scenario_profile:
135
+ break
136
+ else:
137
+ print(f"Select scenario config '{scenario_profile}' is not available")
138
+ print("Available unit scenarios:")
139
+ for available_scenario_profile in attack.scenario_profiles:
140
+ print(f" [+] {available_scenario_profile.name}")
141
+ sys.exit(-1)
142
+
143
+ # Manage lab configuration
144
+ if lab_config_path is None:
145
+ lab_config_dict = {}
146
+ else:
147
+ with open(lab_config_path, "r") as fd:
148
+ yaml_content = fd.read()
149
+ loader = YAML(typ="rt")
150
+ lab_config_dict = loader.load(yaml_content)
151
+ lab_config = LabConfig(**lab_config_dict)
152
+
153
+ # Launch topology
154
+ lab_id: str | None = None
155
+ try:
156
+ if do_run:
157
+ lab_id = scenario_api.run_lab_attack(
158
+ attack,
159
+ scenario_profile,
160
+ lab_config,
161
+ workspace_id,
162
+ args.public,
163
+ )
164
+ else:
165
+ lab_id = scenario_api.create_lab_attack(
166
+ attack,
167
+ scenario_profile,
168
+ lab_config,
169
+ workspace_id,
170
+ args.public,
171
+ )
172
+
173
+ print(f"[+] Lab ID: {lab_id}")
174
+
175
+ if do_run and lab_id is not None:
176
+ wait_lab(lab_id)
177
+
178
+ except Exception as e:
179
+ print(colored(f"Error when running attack {attack_name}: '{e}'", "red"))
180
+ print(traceback.format_exc())
181
+ sys.exit(1)
182
+ finally:
183
+ if do_run and args.destroy_after_scenario and lab_id is not None:
184
+ print("[+] Stopping lab...")
185
+ scenario_api.stop_lab(lab_id)
186
+
187
+
188
+ #
189
+ # 'attack_create_lab_handler' handler
190
+ #
191
+ def attack_create_lab_handler(args: Any) -> None:
192
+ _attack_create_or_run_lab(args, do_run=False)
193
+
194
+
195
+ #
196
+ # 'attack_run_lab_handler' handler
197
+ #
198
+ def attack_run_lab_handler(args: Any) -> None:
199
+ _attack_create_or_run_lab(args, do_run=True)
200
+
201
+
202
+ def add_attack_parser(root_parser: argparse.ArgumentParser, subparsers: Any) -> None:
203
+ # --------------------
204
+ # --- Scenario API options (attack)
205
+ # --------------------
206
+
207
+ parser_attack = subparsers.add_parser(
208
+ "attack",
209
+ help="Scenario API related commands (attacks)",
210
+ formatter_class=root_parser.formatter_class,
211
+ )
212
+ subparsers_attack = parser_attack.add_subparsers()
213
+
214
+ # 'attack_list' command
215
+ parser_attack_list = subparsers_attack.add_parser(
216
+ "list",
217
+ help="List all available attacks",
218
+ formatter_class=root_parser.formatter_class,
219
+ )
220
+ parser_attack_list.set_defaults(func=attack_list_handler)
221
+ parser_attack_list.add_argument(
222
+ "--json", help="Return JSON result.", action="store_true"
223
+ )
224
+
225
+ # 'attack_info' command
226
+ parser_attack_info = subparsers_attack.add_parser(
227
+ "info",
228
+ help="Get information about an attack",
229
+ formatter_class=root_parser.formatter_class,
230
+ )
231
+ parser_attack_info.set_defaults(func=attack_info_handler)
232
+ parser_attack_info.add_argument("attack_name", type=str, help="The attack name")
233
+ parser_attack_info.add_argument(
234
+ "--json", help="Return JSON result.", action="store_true"
235
+ )
236
+
237
+ # 'attack_create_lab' command
238
+ parser_attack_create_lab = subparsers_attack.add_parser(
239
+ "create",
240
+ help="Create a lab with a specific attack",
241
+ formatter_class=root_parser.formatter_class,
242
+ )
243
+ parser_attack_create_lab.set_defaults(func=attack_create_lab_handler)
244
+ parser_attack_create_lab.add_argument(
245
+ "attack_name", type=str, help="The attack name"
246
+ )
247
+ parser_attack_create_lab.add_argument(
248
+ "--destroy",
249
+ action="store_true",
250
+ dest="destroy_after_scenario",
251
+ help="Do not keep the lab up after scenario execution (False by default)",
252
+ )
253
+ parser_attack_create_lab.add_argument(
254
+ "--scenario_profile",
255
+ "-sp",
256
+ dest="scenario_profile",
257
+ help="Allows to define the unit scenario to create_lab for a unit attack",
258
+ )
259
+ parser_attack_create_lab.add_argument(
260
+ "--workspace",
261
+ dest="workspace_id",
262
+ help="The group name that have ownership on lab",
263
+ )
264
+ parser_attack_create_lab.add_argument(
265
+ "--lab_config",
266
+ "-lc",
267
+ action="store",
268
+ required=False,
269
+ dest="lab_config_path",
270
+ help="Input path of a YAML configuration for the scenario to run",
271
+ )
272
+ parser_attack_create_lab.add_argument(
273
+ "--public",
274
+ action="store_true",
275
+ help="Create a public link to access this lab",
276
+ )
277
+
278
+ # 'attack_run_lab' command
279
+ parser_attack_run_lab = subparsers_attack.add_parser(
280
+ "run",
281
+ help="Create and run a lab with a specific attack",
282
+ formatter_class=root_parser.formatter_class,
283
+ )
284
+ parser_attack_run_lab.set_defaults(func=attack_run_lab_handler)
285
+ parser_attack_run_lab.add_argument("attack_name", type=str, help="The attack name")
286
+ parser_attack_run_lab.add_argument(
287
+ "--destroy",
288
+ action="store_true",
289
+ dest="destroy_after_scenario",
290
+ help="Do not keep the lab up after scenario execution (False by default)",
291
+ )
292
+ parser_attack_run_lab.add_argument(
293
+ "--scenario_profile",
294
+ "-sp",
295
+ dest="scenario_profile",
296
+ help="Allows to define the unit scenario to run for a unit attack",
297
+ )
298
+ parser_attack_run_lab.add_argument(
299
+ "--workspace",
300
+ dest="workspace_id",
301
+ help="The group name that have ownership on lab",
302
+ )
303
+ parser_attack_run_lab.add_argument(
304
+ "--lab_config",
305
+ "-lc",
306
+ action="store",
307
+ required=False,
308
+ dest="lab_config_path",
309
+ help="Input path of a YAML configuration for the scenario run",
310
+ )
311
+ parser_attack_run_lab.add_argument(
312
+ "--public",
313
+ action="store_true",
314
+ help="Create a public link to access this lab",
315
+ )
316
+
317
+ parser_attack.set_defaults(func=lambda _: parser_attack.print_help())