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,311 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from mantis_scenario_model.lab_config_model import LabConfig
|
|
8
|
+
from pydantic.json import pydantic_encoder
|
|
9
|
+
from ruamel.yaml import YAML
|
|
10
|
+
|
|
11
|
+
from mantis_api_client import scenario_api
|
|
12
|
+
from mantis_api_client.oidc import get_oidc_client
|
|
13
|
+
from mantis_api_client.utils import colored
|
|
14
|
+
from mantis_api_client.utils import wait_lab
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
#
|
|
18
|
+
# 'scenario_list_handler' handler
|
|
19
|
+
#
|
|
20
|
+
def scenario_list_handler(args: Any) -> None:
|
|
21
|
+
try:
|
|
22
|
+
scenarios = sorted(scenario_api.fetch_scenarios(), key=lambda x: x.name)
|
|
23
|
+
except Exception as e:
|
|
24
|
+
print(colored(f"Error when fetching scenarios: '{e}'", "red"))
|
|
25
|
+
sys.exit(1)
|
|
26
|
+
|
|
27
|
+
if args.json:
|
|
28
|
+
print(json.dumps(scenarios, default=pydantic_encoder))
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
print(f"[+] Available scenarios ({len(scenarios)}):")
|
|
32
|
+
for scenario in scenarios:
|
|
33
|
+
print(f" [+] \033[1m{scenario.name}\033[0m")
|
|
34
|
+
print(f" {scenario.description}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
#
|
|
38
|
+
# 'scenario_info_handler' handler
|
|
39
|
+
#
|
|
40
|
+
def scenario_info_handler(args: Any) -> None:
|
|
41
|
+
# Parameters
|
|
42
|
+
scenario_name = args.scenario_name
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
scenario = scenario_api.fetch_scenario_by_name(scenario_name)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(
|
|
48
|
+
colored(
|
|
49
|
+
f"Error when fetching scenario {scenario_name}: '{e}'",
|
|
50
|
+
"red",
|
|
51
|
+
"on_white",
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
|
|
56
|
+
if args.json:
|
|
57
|
+
print(scenario.json())
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
print("[+] Scenario information:")
|
|
61
|
+
print(f" [+] \033[1mName\033[0m: {scenario.name}")
|
|
62
|
+
print(" [+] \033[1mKeywords\033[0m: ", end="")
|
|
63
|
+
print(", ".join(scenario.keywords))
|
|
64
|
+
print(f" [+] \033[1mDescription\033[0m: {scenario.description}")
|
|
65
|
+
if len(scenario.long_description) > 0:
|
|
66
|
+
print(" [+] \033[1mLong description\033[0m:")
|
|
67
|
+
for ld in scenario.long_description:
|
|
68
|
+
print(f" - {ld}")
|
|
69
|
+
print(f" [+] \033[1mLearning context\033[0m: {scenario.learning_context}")
|
|
70
|
+
print(" [+] \033[1mAvailable scenario profiles\033[0m:")
|
|
71
|
+
for scenario_profile in scenario.scenario_profiles:
|
|
72
|
+
target = f", target: {scenario_profile.compromission.target_name}"
|
|
73
|
+
print(
|
|
74
|
+
f""" [+] {scenario_profile.name} (Topology: {scenario_profile.topology_name}){target}"""
|
|
75
|
+
)
|
|
76
|
+
print(" [+] \033[1mUnit attacks available\033[0m:")
|
|
77
|
+
for attack in scenario.attacks:
|
|
78
|
+
print(f" - {attack}")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _scenario_create_or_run_lab(args: Any, do_run: bool = True):
|
|
82
|
+
# Parameters
|
|
83
|
+
scenario_name = args.scenario_name
|
|
84
|
+
lab_config_path = args.lab_config_path
|
|
85
|
+
scenario_profile = args.scenario_profile
|
|
86
|
+
|
|
87
|
+
if not args.workspace_id:
|
|
88
|
+
try:
|
|
89
|
+
workspace_id = get_oidc_client().get_default_workspace(raise_exc=True)
|
|
90
|
+
except Exception as e:
|
|
91
|
+
print(colored(f"Error when fetching attacks: '{e}'", "red"))
|
|
92
|
+
sys.exit(1)
|
|
93
|
+
else:
|
|
94
|
+
workspace_id = args.workspace_id
|
|
95
|
+
|
|
96
|
+
# Retrieve associated group id
|
|
97
|
+
if workspace_id is None:
|
|
98
|
+
print(colored("You have to specify a workspace with --workspace", "yellow"))
|
|
99
|
+
sys.exit(1)
|
|
100
|
+
|
|
101
|
+
# Manage lab configuration
|
|
102
|
+
if lab_config_path is None:
|
|
103
|
+
lab_config_dict = {}
|
|
104
|
+
else:
|
|
105
|
+
with open(lab_config_path, "r") as fd:
|
|
106
|
+
yaml_content = fd.read()
|
|
107
|
+
loader = YAML(typ="rt")
|
|
108
|
+
lab_config_dict = loader.load(yaml_content)
|
|
109
|
+
lab_config = LabConfig(**lab_config_dict)
|
|
110
|
+
|
|
111
|
+
# Launch scenario
|
|
112
|
+
try:
|
|
113
|
+
scenario = scenario_api.fetch_scenario_by_name(scenario_name)
|
|
114
|
+
print(f"[+] Going to execute scenario: {scenario.name}")
|
|
115
|
+
|
|
116
|
+
if scenario_profile is None:
|
|
117
|
+
print(
|
|
118
|
+
"Needed argument --scenario_profile in order to choose the unit scenario to run"
|
|
119
|
+
)
|
|
120
|
+
print("Available unit scenarios:")
|
|
121
|
+
for available_scenario_profile in scenario.scenario_profiles:
|
|
122
|
+
print(f" [+] {available_scenario_profile.name}")
|
|
123
|
+
sys.exit(-1)
|
|
124
|
+
|
|
125
|
+
if not any(scenario_profile == c.name for c in scenario.scenario_profiles):
|
|
126
|
+
print(
|
|
127
|
+
colored(
|
|
128
|
+
f"Error '{scenario_profile}' not supported for this scenario.",
|
|
129
|
+
"red",
|
|
130
|
+
"on_white",
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
sys.exit(-1)
|
|
134
|
+
|
|
135
|
+
if do_run:
|
|
136
|
+
lab_id = scenario_api.run_lab_scenario(
|
|
137
|
+
scenario,
|
|
138
|
+
scenario_profile,
|
|
139
|
+
lab_config,
|
|
140
|
+
workspace_id,
|
|
141
|
+
args.public,
|
|
142
|
+
)
|
|
143
|
+
else:
|
|
144
|
+
lab_id = scenario_api.create_lab_scenario(
|
|
145
|
+
scenario,
|
|
146
|
+
scenario_profile,
|
|
147
|
+
lab_config,
|
|
148
|
+
workspace_id,
|
|
149
|
+
args.public,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
print(f"[+] Lab ID: {lab_id}")
|
|
153
|
+
|
|
154
|
+
if do_run:
|
|
155
|
+
wait_lab(lab_id)
|
|
156
|
+
|
|
157
|
+
except Exception as e:
|
|
158
|
+
print(colored(f"Error when running scenario {scenario_name}: '{e}'", "red"))
|
|
159
|
+
sys.exit(1)
|
|
160
|
+
finally:
|
|
161
|
+
if do_run and args.destroy_after_scenario:
|
|
162
|
+
print("[+] Stopping lab...")
|
|
163
|
+
scenario_api.stop_lab(lab_id)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
#
|
|
167
|
+
# 'scenario_create_lab_handler' handler
|
|
168
|
+
#
|
|
169
|
+
def scenario_create_lab_handler(args: Any) -> None:
|
|
170
|
+
_scenario_create_or_run_lab(args, do_run=False)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
#
|
|
174
|
+
# 'scenario_run_lab_handler' handler
|
|
175
|
+
#
|
|
176
|
+
def scenario_run_lab_handler(args: Any) -> None:
|
|
177
|
+
_scenario_create_or_run_lab(args, do_run=True)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def add_scenario_parser(
|
|
181
|
+
root_parser: argparse.ArgumentParser,
|
|
182
|
+
subparsers: Any,
|
|
183
|
+
) -> None:
|
|
184
|
+
# --------------------
|
|
185
|
+
# --- Scenario API options (scenario)
|
|
186
|
+
# --------------------
|
|
187
|
+
|
|
188
|
+
parser_scenario = subparsers.add_parser(
|
|
189
|
+
"scenario",
|
|
190
|
+
help="Scenario API related commands (scenario)",
|
|
191
|
+
formatter_class=root_parser.formatter_class,
|
|
192
|
+
)
|
|
193
|
+
subparsers_scenario = parser_scenario.add_subparsers()
|
|
194
|
+
|
|
195
|
+
# 'scenario_list' command
|
|
196
|
+
parser_scenario_list = subparsers_scenario.add_parser(
|
|
197
|
+
"list",
|
|
198
|
+
help="List all available scenarios",
|
|
199
|
+
formatter_class=root_parser.formatter_class,
|
|
200
|
+
)
|
|
201
|
+
parser_scenario_list.set_defaults(func=scenario_list_handler)
|
|
202
|
+
parser_scenario_list.add_argument(
|
|
203
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# 'scenario_info' command
|
|
207
|
+
parser_scenario_info = subparsers_scenario.add_parser(
|
|
208
|
+
"info",
|
|
209
|
+
help="Get information about a scenario",
|
|
210
|
+
formatter_class=root_parser.formatter_class,
|
|
211
|
+
)
|
|
212
|
+
parser_scenario_info.set_defaults(func=scenario_info_handler)
|
|
213
|
+
parser_scenario_info.add_argument(
|
|
214
|
+
"scenario_name", type=str, help="The scenario name"
|
|
215
|
+
)
|
|
216
|
+
parser_scenario_info.add_argument(
|
|
217
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# 'scenario_create_lab' command
|
|
221
|
+
parser_scenario_create_lab = subparsers_scenario.add_parser(
|
|
222
|
+
"create",
|
|
223
|
+
help="Create a lab with a specific scenario",
|
|
224
|
+
formatter_class=root_parser.formatter_class,
|
|
225
|
+
)
|
|
226
|
+
parser_scenario_create_lab.set_defaults(func=scenario_create_lab_handler)
|
|
227
|
+
parser_scenario_create_lab.add_argument(
|
|
228
|
+
"scenario_name", type=str, help="The scenario name"
|
|
229
|
+
)
|
|
230
|
+
parser_scenario_create_lab.add_argument(
|
|
231
|
+
"--workspace",
|
|
232
|
+
dest="workspace_id",
|
|
233
|
+
help="The group ID that have ownership on lab",
|
|
234
|
+
)
|
|
235
|
+
parser_scenario_create_lab.add_argument(
|
|
236
|
+
"--destroy",
|
|
237
|
+
action="store_true",
|
|
238
|
+
dest="destroy_after_scenario",
|
|
239
|
+
help="Do not keep the lab up after scenario execution (False by default)",
|
|
240
|
+
)
|
|
241
|
+
parser_scenario_create_lab.add_argument(
|
|
242
|
+
"--lab_config",
|
|
243
|
+
"-lc",
|
|
244
|
+
action="store",
|
|
245
|
+
required=False,
|
|
246
|
+
dest="lab_config_path",
|
|
247
|
+
help="Input path of a YAML configuration for the scenario to run",
|
|
248
|
+
)
|
|
249
|
+
parser_scenario_create_lab.add_argument(
|
|
250
|
+
"--scenario_profile",
|
|
251
|
+
"-sp",
|
|
252
|
+
required=False,
|
|
253
|
+
dest="scenario_profile",
|
|
254
|
+
help="Allows to define the scenario config to run",
|
|
255
|
+
)
|
|
256
|
+
parser_scenario_create_lab.add_argument(
|
|
257
|
+
"--public",
|
|
258
|
+
action="store_true",
|
|
259
|
+
help="Create a public link to access this lab",
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# 'scenario_run_lab' command
|
|
263
|
+
parser_scenario_run_lab = subparsers_scenario.add_parser(
|
|
264
|
+
"run",
|
|
265
|
+
help="Create and run a lab with a specific scenario",
|
|
266
|
+
formatter_class=root_parser.formatter_class,
|
|
267
|
+
)
|
|
268
|
+
parser_scenario_run_lab.set_defaults(func=scenario_run_lab_handler)
|
|
269
|
+
parser_scenario_run_lab.add_argument(
|
|
270
|
+
"scenario_name", type=str, help="The scenario name"
|
|
271
|
+
)
|
|
272
|
+
parser_scenario_run_lab.add_argument(
|
|
273
|
+
"--workspace",
|
|
274
|
+
dest="workspace_id",
|
|
275
|
+
help="The group ID that have ownership on lab",
|
|
276
|
+
)
|
|
277
|
+
parser_scenario_run_lab.add_argument(
|
|
278
|
+
"--topology",
|
|
279
|
+
action="store",
|
|
280
|
+
required=False,
|
|
281
|
+
dest="topology_file",
|
|
282
|
+
help="Input path of a YAML topology file to override the default one",
|
|
283
|
+
)
|
|
284
|
+
parser_scenario_run_lab.add_argument(
|
|
285
|
+
"--destroy",
|
|
286
|
+
action="store_true",
|
|
287
|
+
dest="destroy_after_scenario",
|
|
288
|
+
help="Do not keep the lab up after scenario execution (False by default)",
|
|
289
|
+
)
|
|
290
|
+
parser_scenario_run_lab.add_argument(
|
|
291
|
+
"--lab_config",
|
|
292
|
+
"-lc",
|
|
293
|
+
action="store",
|
|
294
|
+
required=False,
|
|
295
|
+
dest="lab_config_path",
|
|
296
|
+
help="Input path of a YAML configuration for the scenario run",
|
|
297
|
+
)
|
|
298
|
+
parser_scenario_run_lab.add_argument(
|
|
299
|
+
"--scenario_profile",
|
|
300
|
+
"-sp",
|
|
301
|
+
required=False,
|
|
302
|
+
dest="scenario_profile",
|
|
303
|
+
help="Allows to define the scenario config to run",
|
|
304
|
+
)
|
|
305
|
+
parser_scenario_run_lab.add_argument(
|
|
306
|
+
"--public",
|
|
307
|
+
action="store_true",
|
|
308
|
+
help="Create a public link to access this lab",
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
parser_scenario.set_defaults(func=lambda _: parser_scenario.print_help())
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
from mantis_scenario_model.signature_model import Signature
|
|
9
|
+
from pydantic.json import pydantic_encoder
|
|
10
|
+
|
|
11
|
+
from mantis_api_client import scenario_api
|
|
12
|
+
from mantis_api_client.utils import colored
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
#
|
|
16
|
+
# 'signature_list_handler' handler
|
|
17
|
+
#
|
|
18
|
+
def signature_list_handler(args: Any) -> None:
|
|
19
|
+
try:
|
|
20
|
+
signatures = scenario_api.fetch_signatures()
|
|
21
|
+
except Exception as e:
|
|
22
|
+
print(colored(f"Error when fetching signatures: '{e}'", "red"))
|
|
23
|
+
sys.exit(1)
|
|
24
|
+
|
|
25
|
+
if args.json:
|
|
26
|
+
print(json.dumps(signatures, default=pydantic_encoder))
|
|
27
|
+
return
|
|
28
|
+
for key, signature in sorted(
|
|
29
|
+
signatures.items(), key=lambda kv: kv[1].attack_reference_id # (clé, Signature)
|
|
30
|
+
):
|
|
31
|
+
print(f"[+] \033[1m{signature.attack_reference_id}\033[0m")
|
|
32
|
+
print(f" [+] id: {key}")
|
|
33
|
+
implems = ", ".join(
|
|
34
|
+
imp.implementation_type.value for imp in signature.implementations
|
|
35
|
+
)
|
|
36
|
+
print(f" [+] available implementations: {implems}")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def add_signature_parser(
|
|
40
|
+
root_parser: argparse.ArgumentParser,
|
|
41
|
+
subparsers: Any,
|
|
42
|
+
) -> None:
|
|
43
|
+
# -------------------
|
|
44
|
+
# --- Scenario API options (signatures)
|
|
45
|
+
# -------------------
|
|
46
|
+
|
|
47
|
+
parser_signature = subparsers.add_parser(
|
|
48
|
+
"signature",
|
|
49
|
+
help="Scenario API related commands (signature)",
|
|
50
|
+
formatter_class=root_parser.formatter_class,
|
|
51
|
+
)
|
|
52
|
+
subparsers_signature = parser_signature.add_subparsers()
|
|
53
|
+
|
|
54
|
+
# 'signature_list' command
|
|
55
|
+
parser_signature_list = subparsers_signature.add_parser(
|
|
56
|
+
"list",
|
|
57
|
+
help="List all available signatures",
|
|
58
|
+
formatter_class=root_parser.formatter_class,
|
|
59
|
+
)
|
|
60
|
+
parser_signature_list.set_defaults(func=signature_list_handler)
|
|
61
|
+
parser_signature_list.add_argument(
|
|
62
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
parser_signature_info = subparsers_signature.add_parser(
|
|
66
|
+
"info",
|
|
67
|
+
help="Get information about a signature",
|
|
68
|
+
formatter_class=root_parser.formatter_class,
|
|
69
|
+
)
|
|
70
|
+
parser_signature_info.set_defaults(func=signature_info_handler)
|
|
71
|
+
parser_signature_info.add_argument(
|
|
72
|
+
"signature_id", type=str, help="The signature id"
|
|
73
|
+
)
|
|
74
|
+
parser_signature_info.add_argument(
|
|
75
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
group = parser_signature_info.add_mutually_exclusive_group()
|
|
79
|
+
|
|
80
|
+
# Ajoutez les arguments au groupe
|
|
81
|
+
group.add_argument(
|
|
82
|
+
"--attack",
|
|
83
|
+
help="The signature_id is a reference to an attack name. Cannot be used with --scenario.",
|
|
84
|
+
action="store_true",
|
|
85
|
+
)
|
|
86
|
+
group.add_argument(
|
|
87
|
+
"--scenario",
|
|
88
|
+
help="The signature_id is a reference to a scenario name. Cannot be used with --attack.",
|
|
89
|
+
action="store_true",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
parser_signature.set_defaults(func=lambda _: parser_signature.print_help())
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def print_signatures_of_scenario(signatures: List[Signature]) -> None:
|
|
96
|
+
for sig in signatures:
|
|
97
|
+
print_signature(sig)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def print_signature(signature) -> None:
|
|
101
|
+
|
|
102
|
+
print(f"[+] \033[1m{signature.attack_reference_id}\033[0m")
|
|
103
|
+
print(" [+] available implementations:")
|
|
104
|
+
for i in signature.implementations:
|
|
105
|
+
print(f" [>] {i.implementation_type.value}")
|
|
106
|
+
for k in i.signatures:
|
|
107
|
+
print(f" [+] Format \033[1m{k.signature_type.value}\033[0m")
|
|
108
|
+
print(f" {k.signature}")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def signature_info_handler(args: Any) -> None:
|
|
112
|
+
# Parameters
|
|
113
|
+
signature_id = args.signature_id
|
|
114
|
+
signatures = []
|
|
115
|
+
signature = None
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
if args.attack is True:
|
|
119
|
+
# It's an attack ID
|
|
120
|
+
signature = scenario_api.fetch_signature_by_attack_id(signature_id)
|
|
121
|
+
elif args.scenario is True:
|
|
122
|
+
# It's a scenario ID
|
|
123
|
+
signatures = scenario_api.fetch_signatures_by_scenario_id(signature_id)
|
|
124
|
+
else:
|
|
125
|
+
# It's a signature ID
|
|
126
|
+
signature = scenario_api.fetch_signature_by_signature_id(signature_id)
|
|
127
|
+
except Exception as e:
|
|
128
|
+
print(colored(f"Error when fetching signature {signature_id}: '{e}'", "red"))
|
|
129
|
+
sys.exit(1)
|
|
130
|
+
|
|
131
|
+
if args.json and args.scenario is False and signature is not None:
|
|
132
|
+
print(signature.json())
|
|
133
|
+
return
|
|
134
|
+
elif args.json and args.scenario is True and signatures is not None:
|
|
135
|
+
json_data = [sig.json() for sig in signatures]
|
|
136
|
+
|
|
137
|
+
# Now json_data is a list of JSON strings, but if you want it in the final JSON format, use json.dumps
|
|
138
|
+
json_str = json.dumps(json_data)
|
|
139
|
+
|
|
140
|
+
# Print the final JSON string
|
|
141
|
+
print(json_str)
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
if args.scenario is True:
|
|
145
|
+
print_signatures_of_scenario(signatures)
|
|
146
|
+
else:
|
|
147
|
+
print_signature(signature)
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import argparse
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from mantis_scenario_model.lab_config_model import LabConfig
|
|
8
|
+
from mantis_scenario_model.node import VirtualMachine
|
|
9
|
+
from pydantic.json import pydantic_encoder
|
|
10
|
+
from ruamel.yaml import YAML
|
|
11
|
+
|
|
12
|
+
from mantis_api_client import scenario_api
|
|
13
|
+
from mantis_api_client.oidc import get_oidc_client
|
|
14
|
+
from mantis_api_client.utils import colored
|
|
15
|
+
from mantis_api_client.utils import wait_lab
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
#
|
|
19
|
+
# 'topology_list_handler' handler
|
|
20
|
+
#
|
|
21
|
+
def topology_list_handler(args: Any) -> None:
|
|
22
|
+
try:
|
|
23
|
+
topologies = sorted(scenario_api.fetch_topologies(), key=lambda x: x.name)
|
|
24
|
+
except Exception as e:
|
|
25
|
+
print(colored(f"Error when fetching topologies: '{e}'", "red"))
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
if args.json:
|
|
29
|
+
print(json.dumps(topologies, default=pydantic_encoder))
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
print("[+] Available topologies:")
|
|
33
|
+
for topology in topologies:
|
|
34
|
+
print(f" [+] {topology.name}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
#
|
|
38
|
+
# 'topology_info_handler' handler
|
|
39
|
+
#
|
|
40
|
+
def topology_info_handler(args: Any) -> None:
|
|
41
|
+
# Parameters
|
|
42
|
+
topology_name = args.topology_name
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
topology = scenario_api.fetch_topology_by_name(topology_name)
|
|
46
|
+
except Exception as e:
|
|
47
|
+
print(colored(f"Error when fetching topology {topology_name}: '{e}'", "red"))
|
|
48
|
+
sys.exit(1)
|
|
49
|
+
|
|
50
|
+
if args.json:
|
|
51
|
+
print(topology.json())
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
print("[+] Topology information:")
|
|
55
|
+
print(f" [+] \033[1mName\033[0m: {topology.name}")
|
|
56
|
+
print(" [+] \033[1mNodes\033[0m:")
|
|
57
|
+
for node in topology.nodes:
|
|
58
|
+
if type(node) is VirtualMachine:
|
|
59
|
+
print(f" [+] {node.type}: {node.name} ({node.basebox_id})")
|
|
60
|
+
else:
|
|
61
|
+
print(f" [+] {node.type}: {node.name}")
|
|
62
|
+
print(" [+] \033[1mLinks\033[0m:")
|
|
63
|
+
for link in topology.links:
|
|
64
|
+
print(
|
|
65
|
+
f" [+] {link.switch.name}({link.switch.type}) -- {link.params.ip} -- {link.node.name}({link.node.type})"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _topology_create_or_run_lab(args: Any, do_run: bool = True):
|
|
70
|
+
# Parameters
|
|
71
|
+
topology_name = args.topology_name
|
|
72
|
+
lab_config_path = args.lab_config_path
|
|
73
|
+
|
|
74
|
+
if not args.workspace_id:
|
|
75
|
+
try:
|
|
76
|
+
workspace_id = get_oidc_client().get_default_workspace(raise_exc=True)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
print(colored(f"Error when fetching attacks: '{e}'", "red"))
|
|
79
|
+
sys.exit(1)
|
|
80
|
+
else:
|
|
81
|
+
workspace_id = args.workspace_id
|
|
82
|
+
|
|
83
|
+
# Retrieve associated group id
|
|
84
|
+
if workspace_id is None:
|
|
85
|
+
print(colored("You have to specify a workspace with --workspace", "yellow"))
|
|
86
|
+
sys.exit(1)
|
|
87
|
+
|
|
88
|
+
# Manage lab configuration
|
|
89
|
+
if lab_config_path is None:
|
|
90
|
+
lab_config_dict = {}
|
|
91
|
+
else:
|
|
92
|
+
with open(lab_config_path, "r") as fd:
|
|
93
|
+
yaml_content = fd.read()
|
|
94
|
+
loader = YAML(typ="rt")
|
|
95
|
+
lab_config_dict = loader.load(yaml_content)
|
|
96
|
+
lab_config = LabConfig(**lab_config_dict)
|
|
97
|
+
|
|
98
|
+
# Launch topology
|
|
99
|
+
try:
|
|
100
|
+
topology = scenario_api.fetch_topology_by_name(topology_name)
|
|
101
|
+
print(f"[+] Going to execute topology: {topology.name}")
|
|
102
|
+
|
|
103
|
+
if do_run:
|
|
104
|
+
lab_id = scenario_api.run_lab_topology(
|
|
105
|
+
topology,
|
|
106
|
+
lab_config,
|
|
107
|
+
workspace_id,
|
|
108
|
+
args.public,
|
|
109
|
+
)
|
|
110
|
+
else:
|
|
111
|
+
lab_id = scenario_api.create_lab_topology(
|
|
112
|
+
topology,
|
|
113
|
+
lab_config,
|
|
114
|
+
workspace_id,
|
|
115
|
+
args.public,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
print(f"[+] Lab ID: {lab_id}")
|
|
119
|
+
|
|
120
|
+
if do_run:
|
|
121
|
+
wait_lab(lab_id)
|
|
122
|
+
|
|
123
|
+
except Exception as e:
|
|
124
|
+
print(colored(f"Error when running topology {topology_name}: '{e}'", "red"))
|
|
125
|
+
sys.exit(1)
|
|
126
|
+
finally:
|
|
127
|
+
if do_run and args.destroy_after_scenario:
|
|
128
|
+
print("[+] Stopping lab...")
|
|
129
|
+
scenario_api.stop_lab(lab_id)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
#
|
|
133
|
+
# 'topology_create_lab_handler' handler
|
|
134
|
+
#
|
|
135
|
+
def topology_create_lab_handler(args: Any) -> None:
|
|
136
|
+
_topology_create_or_run_lab(args, do_run=False)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
#
|
|
140
|
+
# 'topology_run_lab_handler' handler
|
|
141
|
+
#
|
|
142
|
+
def topology_run_lab_handler(args: Any) -> None:
|
|
143
|
+
_topology_create_or_run_lab(args, do_run=True)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def add_topology_parser(
|
|
147
|
+
root_parser: argparse.ArgumentParser,
|
|
148
|
+
subparsers: Any,
|
|
149
|
+
) -> None:
|
|
150
|
+
# -------------------
|
|
151
|
+
# --- Scenario API options (topologies)
|
|
152
|
+
# -------------------
|
|
153
|
+
|
|
154
|
+
parser_topology = subparsers.add_parser(
|
|
155
|
+
"topology",
|
|
156
|
+
help="Scenario API related commands (topology)",
|
|
157
|
+
formatter_class=root_parser.formatter_class,
|
|
158
|
+
)
|
|
159
|
+
subparsers_topology = parser_topology.add_subparsers()
|
|
160
|
+
|
|
161
|
+
# 'topology_list' command
|
|
162
|
+
parser_topology_list = subparsers_topology.add_parser(
|
|
163
|
+
"list",
|
|
164
|
+
help="List all available topologies",
|
|
165
|
+
formatter_class=root_parser.formatter_class,
|
|
166
|
+
)
|
|
167
|
+
parser_topology_list.set_defaults(func=topology_list_handler)
|
|
168
|
+
parser_topology_list.add_argument(
|
|
169
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# 'topology_info' command
|
|
173
|
+
parser_topology_info = subparsers_topology.add_parser(
|
|
174
|
+
"info",
|
|
175
|
+
help="Get information about a topology",
|
|
176
|
+
formatter_class=root_parser.formatter_class,
|
|
177
|
+
)
|
|
178
|
+
parser_topology_info.set_defaults(func=topology_info_handler)
|
|
179
|
+
parser_topology_info.add_argument(
|
|
180
|
+
"topology_name", type=str, help="The topology name"
|
|
181
|
+
)
|
|
182
|
+
parser_topology_info.add_argument(
|
|
183
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# 'topology_create_lab' command
|
|
187
|
+
parser_topology_create_lab = subparsers_topology.add_parser(
|
|
188
|
+
"create",
|
|
189
|
+
help="Create a lab with a specific topology",
|
|
190
|
+
formatter_class=root_parser.formatter_class,
|
|
191
|
+
)
|
|
192
|
+
parser_topology_create_lab.set_defaults(func=topology_create_lab_handler)
|
|
193
|
+
parser_topology_create_lab.add_argument(
|
|
194
|
+
"topology_name", type=str, help="The topology name"
|
|
195
|
+
)
|
|
196
|
+
parser_topology_create_lab.add_argument(
|
|
197
|
+
"--destroy",
|
|
198
|
+
action="store_true",
|
|
199
|
+
dest="destroy_after_scenario",
|
|
200
|
+
help="Do not keep the lab up after topology execution (False by default)",
|
|
201
|
+
)
|
|
202
|
+
parser_topology_create_lab.add_argument(
|
|
203
|
+
"--workspace",
|
|
204
|
+
dest="workspace_id",
|
|
205
|
+
help="The group ID that have ownership on lab",
|
|
206
|
+
)
|
|
207
|
+
parser_topology_create_lab.add_argument(
|
|
208
|
+
"--lab_config",
|
|
209
|
+
action="store",
|
|
210
|
+
required=False,
|
|
211
|
+
dest="lab_config_path",
|
|
212
|
+
help="Input path of a YAML configuration for the scenario to run",
|
|
213
|
+
)
|
|
214
|
+
parser_topology_create_lab.add_argument(
|
|
215
|
+
"--public",
|
|
216
|
+
action="store_true",
|
|
217
|
+
help="Create a public link to access this lab",
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
# 'topology_run_lab' command
|
|
221
|
+
parser_topology_run_lab = subparsers_topology.add_parser(
|
|
222
|
+
"run",
|
|
223
|
+
help="Create and run a lab with a specific topology",
|
|
224
|
+
formatter_class=root_parser.formatter_class,
|
|
225
|
+
)
|
|
226
|
+
parser_topology_run_lab.set_defaults(func=topology_run_lab_handler)
|
|
227
|
+
parser_topology_run_lab.add_argument(
|
|
228
|
+
"topology_name", type=str, help="The topology name"
|
|
229
|
+
)
|
|
230
|
+
parser_topology_run_lab.add_argument(
|
|
231
|
+
"--destroy",
|
|
232
|
+
action="store_true",
|
|
233
|
+
dest="destroy_after_scenario",
|
|
234
|
+
help="Do not keep the lab up after topology execution (False by default)",
|
|
235
|
+
)
|
|
236
|
+
parser_topology_run_lab.add_argument(
|
|
237
|
+
"--workspace",
|
|
238
|
+
dest="workspace_id",
|
|
239
|
+
help="The group ID that have ownership on lab",
|
|
240
|
+
)
|
|
241
|
+
parser_topology_run_lab.add_argument(
|
|
242
|
+
"--lab_config",
|
|
243
|
+
"-lc",
|
|
244
|
+
action="store",
|
|
245
|
+
required=False,
|
|
246
|
+
dest="lab_config_path",
|
|
247
|
+
help="Input path of a YAML configuration for the scenario run",
|
|
248
|
+
)
|
|
249
|
+
parser_topology_run_lab.add_argument(
|
|
250
|
+
"--public",
|
|
251
|
+
action="store_true",
|
|
252
|
+
help="Create a public link to access this lab",
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
parser_topology.set_defaults(func=lambda _: parser_topology.print_help())
|