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,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())
|