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,805 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import argparse
|
|
3
|
+
import datetime
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import pprint
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from mantis_scenario_model.lab_model import Lab
|
|
11
|
+
|
|
12
|
+
import mantis_api_client.scenario_api as scenario_api
|
|
13
|
+
from cr_api_client.cli_parser.provisioning_parser import add_provisioning_parser
|
|
14
|
+
from mantis_api_client import user_api
|
|
15
|
+
from mantis_api_client.cli_parser.redteam_parser import add_redteam_parser
|
|
16
|
+
from mantis_api_client.oidc import get_oidc_client
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
#
|
|
20
|
+
# 'lab_info_handler' handler
|
|
21
|
+
#
|
|
22
|
+
def lab_info_handler(args: Any) -> None:
|
|
23
|
+
# Parameters
|
|
24
|
+
lab_id = args.lab_id
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
ret = scenario_api.fetch_lab(lab_id)
|
|
28
|
+
lab: Lab = Lab(**ret)
|
|
29
|
+
except Exception as e:
|
|
30
|
+
print(f"Error when fetching lab: '{e}'")
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
|
|
33
|
+
if args.json:
|
|
34
|
+
print(lab.json())
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
lab_creation_timestamp = (
|
|
38
|
+
datetime.datetime.fromtimestamp(
|
|
39
|
+
lab.lab_creation_timestamp, datetime.timezone.utc
|
|
40
|
+
).strftime("%Y-%m-%d %H:%M:%S")
|
|
41
|
+
+ " UTC"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
created_by = user_api.fetch_user(lab.created_by)
|
|
45
|
+
|
|
46
|
+
print("[+] Lab information:")
|
|
47
|
+
print(f""" [+] \033[1mContent\033[0m: {lab.name}""")
|
|
48
|
+
print(f""" [+] \033[1mType\033[0m: {lab.content_type.value}""")
|
|
49
|
+
print(f""" [+] \033[1mLab ID\033[0m: {lab.runner_id}""")
|
|
50
|
+
print(
|
|
51
|
+
f""" [+] \033[1mCreated by\033[0m: {created_by['given_name']} {created_by['last_name']} ({lab.created_by})"""
|
|
52
|
+
)
|
|
53
|
+
print(f""" [+] \033[1mStatus\033[0m: {lab.status.value}""")
|
|
54
|
+
|
|
55
|
+
print(""" [+] \033[1mTimestamps:""")
|
|
56
|
+
|
|
57
|
+
lab_creation_timestamp = (
|
|
58
|
+
datetime.datetime.fromtimestamp(
|
|
59
|
+
lab.lab_creation_timestamp, datetime.timezone.utc
|
|
60
|
+
).strftime("%Y-%m-%d %H:%M:%S")
|
|
61
|
+
+ " UTC"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
if lab.lab_start_timestamp is not None:
|
|
65
|
+
lab_start_timestamp = (
|
|
66
|
+
datetime.datetime.fromtimestamp(
|
|
67
|
+
lab.lab_start_timestamp, datetime.timezone.utc
|
|
68
|
+
).strftime("%Y-%m-%d %H:%M:%S")
|
|
69
|
+
+ " UTC"
|
|
70
|
+
)
|
|
71
|
+
else:
|
|
72
|
+
lab_start_timestamp = str(lab.lab_start_timestamp)
|
|
73
|
+
|
|
74
|
+
if lab.lab_content_end_timestamp is not None:
|
|
75
|
+
lab_content_end_timestamp = (
|
|
76
|
+
datetime.datetime.fromtimestamp(
|
|
77
|
+
lab.lab_content_end_timestamp, datetime.timezone.utc
|
|
78
|
+
).strftime("%Y-%m-%d %H:%M:%S")
|
|
79
|
+
+ " UTC"
|
|
80
|
+
)
|
|
81
|
+
else:
|
|
82
|
+
lab_content_end_timestamp = str(lab.lab_content_end_timestamp)
|
|
83
|
+
|
|
84
|
+
if lab.lab_end_timestamp is not None:
|
|
85
|
+
lab_end_timestamp = (
|
|
86
|
+
datetime.datetime.fromtimestamp(
|
|
87
|
+
lab.lab_end_timestamp, datetime.timezone.utc
|
|
88
|
+
).strftime("%Y-%m-%d %H:%M:%S")
|
|
89
|
+
+ " UTC"
|
|
90
|
+
)
|
|
91
|
+
else:
|
|
92
|
+
lab_end_timestamp = str(lab.lab_end_timestamp)
|
|
93
|
+
|
|
94
|
+
print(f""" [+] \033[1mCreation time\033[0m: {lab_creation_timestamp}""")
|
|
95
|
+
print(f""" [+] \033[1mStart time\033[0m: {lab_start_timestamp}""")
|
|
96
|
+
print(f""" [+] \033[1mContent end time\033[0m: {lab_content_end_timestamp}""")
|
|
97
|
+
print(f""" [+] \033[1mLab end time\033[0m: {lab_end_timestamp}""")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
#
|
|
101
|
+
# 'lab_api_handler' handler
|
|
102
|
+
#
|
|
103
|
+
def lab_api_handler(args: Any) -> None:
|
|
104
|
+
# Parameters
|
|
105
|
+
lab_id = args.lab_id
|
|
106
|
+
|
|
107
|
+
active_profile_domain = get_oidc_client().get_active_profile_domain(raise_exc=False)
|
|
108
|
+
if not active_profile_domain:
|
|
109
|
+
print("[+] Not authenticated")
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
it_simulation_api_url = (
|
|
113
|
+
f"https://app.{active_profile_domain}/proxy/{lab_id}/api/it_simulation"
|
|
114
|
+
)
|
|
115
|
+
provisioning_api_url = (
|
|
116
|
+
f"https://app.{active_profile_domain}/proxy/{lab_id}/api/provisioning"
|
|
117
|
+
)
|
|
118
|
+
user_activity_api_url = (
|
|
119
|
+
f"https://app.{active_profile_domain}/proxy/{lab_id}/api/user_activity"
|
|
120
|
+
)
|
|
121
|
+
redteam_api_url = f"https://app.{active_profile_domain}/proxy/{lab_id}/api/redteam"
|
|
122
|
+
|
|
123
|
+
if args.json:
|
|
124
|
+
print(
|
|
125
|
+
json.dumps(
|
|
126
|
+
{
|
|
127
|
+
"it_simulation_api_url": it_simulation_api_url,
|
|
128
|
+
"provisioning_api_url": provisioning_api_url,
|
|
129
|
+
"user_activity_api_url": user_activity_api_url,
|
|
130
|
+
"redteam_api_url": redteam_api_url,
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
)
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
print("[+] Lab APIs:")
|
|
137
|
+
print(f""" [+] \033[1mIT simulation API URL\033[0m: {it_simulation_api_url}""")
|
|
138
|
+
print(f""" [+] \033[1mProvisioning API URL\033[0m: {provisioning_api_url}""")
|
|
139
|
+
print(f""" [+] \033[1mUser activity API URL\033[0m: {user_activity_api_url}""")
|
|
140
|
+
print(f""" [+] \033[1mRedteam API URL\033[0m: {redteam_api_url}""")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
#
|
|
144
|
+
# 'lab_run_handler' handler
|
|
145
|
+
#
|
|
146
|
+
def lab_run_handler(args: Any) -> None:
|
|
147
|
+
# Parameters
|
|
148
|
+
lab_id = args.lab_id
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
scenario_api.run_lab(lab_id)
|
|
152
|
+
except Exception as e:
|
|
153
|
+
print(f"Error when running lab: '{e}'")
|
|
154
|
+
sys.exit(1)
|
|
155
|
+
|
|
156
|
+
print(f"[+] Lab '{lab_id}' is running")
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
#
|
|
160
|
+
# 'lab_stop_handler' handler
|
|
161
|
+
#
|
|
162
|
+
def lab_stop_handler(args: Any) -> None:
|
|
163
|
+
# Parameters
|
|
164
|
+
lab_id = args.lab_id
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
scenario_api.stop_lab(lab_id)
|
|
168
|
+
except Exception as e:
|
|
169
|
+
print(f"Error when stopping lab: '{e}'")
|
|
170
|
+
sys.exit(1)
|
|
171
|
+
|
|
172
|
+
print(f"[+] Lab '{lab_id}' stopped")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
#
|
|
176
|
+
# 'lab_delete_lab_handler' handler
|
|
177
|
+
#
|
|
178
|
+
def lab_delete_lab_handler(args: Any) -> None:
|
|
179
|
+
# Parameters
|
|
180
|
+
lab_id = args.lab_id
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
scenario_api.delete_lab(lab_id)
|
|
184
|
+
except Exception as e:
|
|
185
|
+
print(f"Error when deleting lab ID {lab_id}: '{e}'")
|
|
186
|
+
sys.exit(1)
|
|
187
|
+
|
|
188
|
+
print(f"[+] Lab '{lab_id}' deleted")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
#
|
|
192
|
+
# 'lab_resume_handler' handler
|
|
193
|
+
#
|
|
194
|
+
def lab_resume_handler(args: Any) -> None:
|
|
195
|
+
# Parameters
|
|
196
|
+
lab_id = args.lab_id
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
scenario_api.resume_lab(lab_id)
|
|
200
|
+
except Exception as e:
|
|
201
|
+
print(f"Error when resume lab: '{e}'")
|
|
202
|
+
sys.exit(1)
|
|
203
|
+
|
|
204
|
+
print(f"[+] Lab '{lab_id}' resumed")
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
#
|
|
208
|
+
# 'lab_paused_status' handler
|
|
209
|
+
#
|
|
210
|
+
def lab_paused_status_handler(args: Any) -> None:
|
|
211
|
+
# Parameters
|
|
212
|
+
lab_id = args.lab_id
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
paused_status = scenario_api.fetch_lab_paused_status(lab_id)
|
|
216
|
+
except Exception as e:
|
|
217
|
+
print(f"Error when fetching lab paused status: '{e}'")
|
|
218
|
+
sys.exit(1)
|
|
219
|
+
|
|
220
|
+
if args.json:
|
|
221
|
+
print(paused_status.json())
|
|
222
|
+
return
|
|
223
|
+
|
|
224
|
+
if paused_status.is_before_step is None:
|
|
225
|
+
position = None
|
|
226
|
+
elif paused_status.is_before_step is True:
|
|
227
|
+
position = "before"
|
|
228
|
+
else:
|
|
229
|
+
position = "after"
|
|
230
|
+
|
|
231
|
+
print(f"[+] Lab '{lab_id}' paused status:")
|
|
232
|
+
print(f" [+] Step: {paused_status.step}")
|
|
233
|
+
print(f" [+] Paused: {position}")
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
#
|
|
237
|
+
# 'lab_topology_handler' handler
|
|
238
|
+
#
|
|
239
|
+
def lab_topology_handler(args: Any) -> None:
|
|
240
|
+
# Parameters
|
|
241
|
+
lab_id = args.lab_id
|
|
242
|
+
|
|
243
|
+
try:
|
|
244
|
+
topology = scenario_api.fetch_lab_topology(lab_id)
|
|
245
|
+
except Exception as e:
|
|
246
|
+
print(f"Error when fetching scenario topology: '{e}'")
|
|
247
|
+
sys.exit(1)
|
|
248
|
+
|
|
249
|
+
if args.json:
|
|
250
|
+
print(topology.json())
|
|
251
|
+
return
|
|
252
|
+
|
|
253
|
+
print("[+] Lab topology")
|
|
254
|
+
print(" [+] Nodes")
|
|
255
|
+
for node in topology.nodes:
|
|
256
|
+
print(f" [+] {node.name} ({node.type})")
|
|
257
|
+
print(" [+] Links")
|
|
258
|
+
for node in topology.nodes:
|
|
259
|
+
if node.type == "switch":
|
|
260
|
+
print(f" [+] {node.name}")
|
|
261
|
+
for link in topology.links:
|
|
262
|
+
if link.switch.name == node.name:
|
|
263
|
+
print(f" [+] {link.node.name} - {link.params.ip}")
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
#
|
|
267
|
+
# 'lab_nodes_handler' handler
|
|
268
|
+
#
|
|
269
|
+
def lab_nodes_handler(args: Any) -> None:
|
|
270
|
+
# Parameters
|
|
271
|
+
lab_id = args.lab_id
|
|
272
|
+
|
|
273
|
+
try:
|
|
274
|
+
nodes = scenario_api.fetch_lab_nodes(lab_id)
|
|
275
|
+
except Exception as e:
|
|
276
|
+
print(f"Error when fetching scenario nodes: '{e}'")
|
|
277
|
+
sys.exit(1)
|
|
278
|
+
|
|
279
|
+
if args.json:
|
|
280
|
+
print(json.dumps(nodes))
|
|
281
|
+
return
|
|
282
|
+
|
|
283
|
+
print("[+] Lab nodes")
|
|
284
|
+
for node in nodes:
|
|
285
|
+
if node["type"] == "switch":
|
|
286
|
+
continue
|
|
287
|
+
print(f" [+] {node['name']} ({node['type']})")
|
|
288
|
+
print(" [+] network interfaces")
|
|
289
|
+
|
|
290
|
+
for ni in node["network_interfaces"]:
|
|
291
|
+
if ni["ip_address_runtime"] is not None:
|
|
292
|
+
ip_address = ni["ip_address_runtime"]
|
|
293
|
+
else:
|
|
294
|
+
ip_address = ni["ip_address"]
|
|
295
|
+
print(f" - {ip_address}")
|
|
296
|
+
|
|
297
|
+
if node["type"] == "virtual_machine":
|
|
298
|
+
print(" [+] Credentials")
|
|
299
|
+
print(
|
|
300
|
+
f" - username: {node['username']} - password: {node['password']}"
|
|
301
|
+
)
|
|
302
|
+
print(
|
|
303
|
+
f" - admin_username: {node['admin_username']} - admin_password: {node['admin_password']}"
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
#
|
|
308
|
+
# 'lab_assets_handler' handler
|
|
309
|
+
#
|
|
310
|
+
def lab_assets_handler(args: Any) -> None:
|
|
311
|
+
# Parameters
|
|
312
|
+
lab_id = args.lab_id
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
assets = scenario_api.fetch_lab_assets(lab_id)
|
|
316
|
+
except Exception as e:
|
|
317
|
+
print(f"Error when fetching scenario assets: '{e}'")
|
|
318
|
+
sys.exit(1)
|
|
319
|
+
|
|
320
|
+
if args.json:
|
|
321
|
+
print(json.dumps(assets))
|
|
322
|
+
return
|
|
323
|
+
|
|
324
|
+
print("[+] Lab assets")
|
|
325
|
+
for asset in assets:
|
|
326
|
+
print(f" [+] {asset['name']} ({asset['type']})")
|
|
327
|
+
print(f" [+] roles: {asset['roles']}")
|
|
328
|
+
if asset["type"] == "virtual_machine":
|
|
329
|
+
print(f" [+] os: {asset['os']} ({asset['os_family']})")
|
|
330
|
+
print(" [+] network interfaces")
|
|
331
|
+
for ni in asset["network_interfaces"]:
|
|
332
|
+
print(f" - {ni['ipv4']}")
|
|
333
|
+
print(" [+] CPE IDs:")
|
|
334
|
+
for cpe in asset["cpes"]:
|
|
335
|
+
print(f" - {cpe}")
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
#
|
|
339
|
+
# 'lab_attack_report_handler' handler
|
|
340
|
+
#
|
|
341
|
+
def lab_attack_report_handler(args: Any) -> None:
|
|
342
|
+
# Parameters
|
|
343
|
+
lab_id = args.lab_id
|
|
344
|
+
|
|
345
|
+
try:
|
|
346
|
+
attack_report = scenario_api.fetch_lab_attack_report(lab_id)
|
|
347
|
+
except Exception as e:
|
|
348
|
+
print(f"Error when fetching scenario attack_report: '{e}'")
|
|
349
|
+
sys.exit(1)
|
|
350
|
+
|
|
351
|
+
if args.json:
|
|
352
|
+
print(json.dumps(attack_report))
|
|
353
|
+
return
|
|
354
|
+
|
|
355
|
+
print("[+] Lab attack report:")
|
|
356
|
+
pp = pprint.PrettyPrinter(width=160)
|
|
357
|
+
pp.pprint(attack_report)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
#
|
|
361
|
+
# 'lab_attack_infras_handler' handler
|
|
362
|
+
#
|
|
363
|
+
def lab_attack_infras_handler(args: Any) -> None:
|
|
364
|
+
# Parameters
|
|
365
|
+
lab_id = args.lab_id
|
|
366
|
+
|
|
367
|
+
try:
|
|
368
|
+
attack_infras = scenario_api.fetch_lab_attack_infras(lab_id)
|
|
369
|
+
except Exception as e:
|
|
370
|
+
print(f"Error when fetching scenario attack_infras: '{e}'")
|
|
371
|
+
sys.exit(1)
|
|
372
|
+
|
|
373
|
+
if args.json:
|
|
374
|
+
print(json.dumps(attack_infras))
|
|
375
|
+
return
|
|
376
|
+
|
|
377
|
+
print("[+] Lab attack infrastructures:")
|
|
378
|
+
for infra in attack_infras:
|
|
379
|
+
print(f" [+] {infra}")
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
#
|
|
383
|
+
# 'lab_attack_sessions_handler' handler
|
|
384
|
+
#
|
|
385
|
+
def lab_attack_sessions_handler(args: Any) -> None:
|
|
386
|
+
# Parameters
|
|
387
|
+
lab_id = args.lab_id
|
|
388
|
+
|
|
389
|
+
try:
|
|
390
|
+
attack_sessions = scenario_api.fetch_lab_attack_sessions(lab_id)
|
|
391
|
+
except Exception as e:
|
|
392
|
+
print(f"Error when fetching scenario attack_sessions: '{e}'")
|
|
393
|
+
sys.exit(1)
|
|
394
|
+
|
|
395
|
+
if args.json:
|
|
396
|
+
print(json.dumps(attack_sessions))
|
|
397
|
+
return
|
|
398
|
+
|
|
399
|
+
knowledge = scenario_api.fetch_lab_attack_knowledge(lab_id)
|
|
400
|
+
|
|
401
|
+
print("[+] Lab attack sessions:")
|
|
402
|
+
for session in attack_sessions:
|
|
403
|
+
compromised_host_ip = None
|
|
404
|
+
if "hosts" in knowledge:
|
|
405
|
+
for host in knowledge["hosts"]:
|
|
406
|
+
for nic in host:
|
|
407
|
+
if nic is not None:
|
|
408
|
+
if "ip" in nic and "idHost" in nic:
|
|
409
|
+
if nic["idHost"] == session["idHost"]:
|
|
410
|
+
compromised_host_ip = nic["ip"]
|
|
411
|
+
print(
|
|
412
|
+
f" [+] {session['idAttackSession']} - compromised host: {compromised_host_ip} - type: {session['type']} - direct_access: {session['direct_access']} - privilege_level: {session['privilege_level']} - uuid: {session['identifier']}"
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
#
|
|
417
|
+
# 'lab_attack_knowledge_handler' handler
|
|
418
|
+
#
|
|
419
|
+
def lab_attack_knowledge_handler(args: Any) -> None:
|
|
420
|
+
# Parameters
|
|
421
|
+
lab_id = args.lab_id
|
|
422
|
+
|
|
423
|
+
try:
|
|
424
|
+
attack_knowledge = scenario_api.fetch_lab_attack_knowledge(lab_id)
|
|
425
|
+
except Exception as e:
|
|
426
|
+
print(f"Error when fetching scenario attack_knowledge: '{e}'")
|
|
427
|
+
sys.exit(1)
|
|
428
|
+
|
|
429
|
+
if args.json:
|
|
430
|
+
print(json.dumps(attack_knowledge))
|
|
431
|
+
return
|
|
432
|
+
|
|
433
|
+
print("[+] Lab attack knowledge:")
|
|
434
|
+
pp = pprint.PrettyPrinter(compact=True, width=160)
|
|
435
|
+
pp.pprint(attack_knowledge)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
#
|
|
439
|
+
# 'lab_notifications_handler' handler
|
|
440
|
+
#
|
|
441
|
+
def lab_notifications_handler(args: Any) -> None:
|
|
442
|
+
# Parameters
|
|
443
|
+
lab_id = args.lab_id
|
|
444
|
+
|
|
445
|
+
try:
|
|
446
|
+
notifications = scenario_api.fetch_lab_notifications(lab_id)
|
|
447
|
+
except Exception as e:
|
|
448
|
+
print(f"Error when fetching scenario notifications: '{e}'")
|
|
449
|
+
sys.exit(1)
|
|
450
|
+
|
|
451
|
+
if args.json:
|
|
452
|
+
print(json.dumps(notifications))
|
|
453
|
+
return
|
|
454
|
+
|
|
455
|
+
print("[+] Lab notifications")
|
|
456
|
+
for notification in notifications:
|
|
457
|
+
event = json.loads(notification)
|
|
458
|
+
print(f"⚡ {event['event_datetime']} - {event['event_data']}")
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
#
|
|
462
|
+
# 'lab_config_handler' handler
|
|
463
|
+
#
|
|
464
|
+
def lab_config_handler(args: Any) -> None:
|
|
465
|
+
# Parameters
|
|
466
|
+
lab_id = args.lab_id
|
|
467
|
+
|
|
468
|
+
try:
|
|
469
|
+
lab_config = scenario_api.fetch_lab_config(lab_id)
|
|
470
|
+
except Exception as e:
|
|
471
|
+
print(f"Error when fetching lab config: '{e}'")
|
|
472
|
+
sys.exit(1)
|
|
473
|
+
|
|
474
|
+
if args.json:
|
|
475
|
+
print(lab_config.model_dump(mode="json"))
|
|
476
|
+
return
|
|
477
|
+
|
|
478
|
+
print("[+] Lab lab_config:")
|
|
479
|
+
pp = pprint.PrettyPrinter(width=160)
|
|
480
|
+
pp.pprint(lab_config.dict())
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def set_current_lab(lab_id: str):
|
|
484
|
+
"""Setting current lab means activating environment variables to
|
|
485
|
+
set cr_api_client lib access to a specific running Cyber Range
|
|
486
|
+
instance.
|
|
487
|
+
|
|
488
|
+
"""
|
|
489
|
+
|
|
490
|
+
active_profile_domain = get_oidc_client().get_active_profile_domain(raise_exc=False)
|
|
491
|
+
if not active_profile_domain:
|
|
492
|
+
print("[+] Not authenticated")
|
|
493
|
+
return
|
|
494
|
+
|
|
495
|
+
os.environ["CORE_API_URL"] = (
|
|
496
|
+
f"https://app.{active_profile_domain}/proxy/{lab_id}/api/it_simulation"
|
|
497
|
+
)
|
|
498
|
+
os.environ["PROVISIONING_API_URL"] = (
|
|
499
|
+
f"https://app.{active_profile_domain}/proxy/{lab_id}/api/provisioning"
|
|
500
|
+
)
|
|
501
|
+
os.environ["USER_ACTIVITY_API_URL"] = (
|
|
502
|
+
f"https://app.{active_profile_domain}/proxy/{lab_id}/api/user_activity"
|
|
503
|
+
)
|
|
504
|
+
os.environ["REDTEAM_API_URL"] = (
|
|
505
|
+
f"https://app.{active_profile_domain}/proxy/{lab_id}/api/redteam"
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def unset_current_lab():
|
|
510
|
+
"""Unsetting current lab means deactivating environment variables used to
|
|
511
|
+
set cr_api_client lib access to a specific running Cyber Range
|
|
512
|
+
instance.
|
|
513
|
+
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
del os.environ["CORE_API_URL"]
|
|
517
|
+
del os.environ["PROVISIONING_API_URL"]
|
|
518
|
+
del os.environ["USER_ACTIVITY_API_URL"]
|
|
519
|
+
del os.environ["REDTEAM_API_URL"]
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def lab_set_lab_handler(args: Any) -> None:
|
|
523
|
+
# Parameters
|
|
524
|
+
lab_id = args.lab_id
|
|
525
|
+
|
|
526
|
+
set_current_lab(lab_id)
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def lab_unset_lab_handler(args: Any) -> None:
|
|
530
|
+
unset_current_lab()
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
#
|
|
534
|
+
# 'lab_get_remote_access_handler' handler
|
|
535
|
+
#
|
|
536
|
+
def lab_get_remote_access_handler(args: Any) -> None:
|
|
537
|
+
# Parameters
|
|
538
|
+
lab_id = args.lab_id
|
|
539
|
+
|
|
540
|
+
try:
|
|
541
|
+
remote_access = scenario_api.fetch_lab_remote_access(lab_id)
|
|
542
|
+
except Exception as e:
|
|
543
|
+
print(f"Error when fetching lab remote access info: '{e}'")
|
|
544
|
+
sys.exit(1)
|
|
545
|
+
|
|
546
|
+
if args.json:
|
|
547
|
+
print(remote_access.json())
|
|
548
|
+
return
|
|
549
|
+
|
|
550
|
+
print("[+] Lab remote access information:")
|
|
551
|
+
print(f"[+] \033[1mLab ID\033[0m: {lab_id}")
|
|
552
|
+
for node in remote_access.nodes:
|
|
553
|
+
print(f" [+] \033[1mNode name\033[0m: {node.name}")
|
|
554
|
+
print(f" [+] \033[1mHTTP URL\033[0m: {node.http_url}")
|
|
555
|
+
|
|
556
|
+
if len(node.credentials) > 0:
|
|
557
|
+
print(" [+] \033[1mCredentials\033[0m:")
|
|
558
|
+
for credential in node.credentials:
|
|
559
|
+
print(" ----")
|
|
560
|
+
print(f" [+] \033[1mLogin\033[0m: {credential.login}")
|
|
561
|
+
print(f" [+] \033[1mPassword\033[0m: {credential.password}")
|
|
562
|
+
print(f" [+] \033[1mPrivilege\033[0m: {credential.privilege}")
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
#
|
|
566
|
+
# 'lab_get_security_alerts_handler' handler
|
|
567
|
+
#
|
|
568
|
+
def lab_get_security_alerts_handler(args: Any) -> None:
|
|
569
|
+
# Parameters
|
|
570
|
+
lab_id = args.lab_id
|
|
571
|
+
|
|
572
|
+
try:
|
|
573
|
+
security_alerts = scenario_api.fetch_lab_security_alerts(lab_id)
|
|
574
|
+
except Exception as e:
|
|
575
|
+
print(f"Error when fetching lab security alerts: '{e}'")
|
|
576
|
+
sys.exit(1)
|
|
577
|
+
|
|
578
|
+
if args.json:
|
|
579
|
+
for security_product, alerts in security_alerts.items():
|
|
580
|
+
for alert in alerts:
|
|
581
|
+
print(alert.json())
|
|
582
|
+
return
|
|
583
|
+
|
|
584
|
+
print(f"[+] \033[1mLab ID\033[0m: {lab_id}")
|
|
585
|
+
for security_product, alerts in security_alerts.items():
|
|
586
|
+
print(f"[+] \033[1mAlerts from {security_product}:\033[0m")
|
|
587
|
+
|
|
588
|
+
for alert in alerts:
|
|
589
|
+
print(f" [+] \033[1mAlert ID\033[0m: {alert.alert_id}")
|
|
590
|
+
print(f" \033[1mAlert name\033[0m: {alert.alert_name}")
|
|
591
|
+
print(f" \033[1mAlert status\033[0m: {alert.alert_status.value}")
|
|
592
|
+
print(f" \033[1mTarget\033[0m: {alert.asset_hostname}")
|
|
593
|
+
print(
|
|
594
|
+
f" \033[1mAssociated log dates\033[0m: start:{alert.start_time} - end:{alert.end_time}"
|
|
595
|
+
)
|
|
596
|
+
print(
|
|
597
|
+
f" \033[1mCorrelated attack IDs\033[0m: {alert.correlated_attacks}"
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def add_lab_parser(root_parser: argparse.ArgumentParser, subparsers: Any) -> None:
|
|
602
|
+
# Subparser lab
|
|
603
|
+
parser_lab = subparsers.add_parser(
|
|
604
|
+
"lab",
|
|
605
|
+
help="Scenario API related commands (lab)",
|
|
606
|
+
formatter_class=root_parser.formatter_class,
|
|
607
|
+
)
|
|
608
|
+
parser_lab.set_defaults(set_lab=lab_set_lab_handler)
|
|
609
|
+
parser_lab.set_defaults(unset_lab=lab_unset_lab_handler)
|
|
610
|
+
parser_lab.add_argument("lab_id", type=str, help="The lab id")
|
|
611
|
+
subparsers_lab = parser_lab.add_subparsers()
|
|
612
|
+
|
|
613
|
+
# 'lab_info' command
|
|
614
|
+
parser_lab_info = subparsers_lab.add_parser(
|
|
615
|
+
"info",
|
|
616
|
+
help="Get information about a lab",
|
|
617
|
+
formatter_class=root_parser.formatter_class,
|
|
618
|
+
)
|
|
619
|
+
parser_lab_info.set_defaults(func=lab_info_handler)
|
|
620
|
+
parser_lab_info.add_argument(
|
|
621
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
# 'lab_api' command
|
|
625
|
+
parser_lab_api = subparsers_lab.add_parser(
|
|
626
|
+
"api",
|
|
627
|
+
help="Get API URLs to directly access the lab",
|
|
628
|
+
formatter_class=root_parser.formatter_class,
|
|
629
|
+
)
|
|
630
|
+
parser_lab_api.set_defaults(func=lab_api_handler)
|
|
631
|
+
parser_lab_api.add_argument(
|
|
632
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
# 'lab_run' command
|
|
636
|
+
parser_lab_run = subparsers_lab.add_parser(
|
|
637
|
+
"run", help="Run a specific lab", formatter_class=root_parser.formatter_class
|
|
638
|
+
)
|
|
639
|
+
parser_lab_run.set_defaults(func=lab_run_handler)
|
|
640
|
+
|
|
641
|
+
# 'lab_stop' command
|
|
642
|
+
parser_lab_stop = subparsers_lab.add_parser(
|
|
643
|
+
"stop", help="Stop a specific lab", formatter_class=root_parser.formatter_class
|
|
644
|
+
)
|
|
645
|
+
parser_lab_stop.set_defaults(func=lab_stop_handler)
|
|
646
|
+
|
|
647
|
+
# 'lab_delete_lab' command
|
|
648
|
+
parser_lab_delete_lab = subparsers_lab.add_parser(
|
|
649
|
+
"delete",
|
|
650
|
+
help="Delete lab from its lab id",
|
|
651
|
+
formatter_class=root_parser.formatter_class,
|
|
652
|
+
)
|
|
653
|
+
parser_lab_delete_lab.set_defaults(func=lab_delete_lab_handler)
|
|
654
|
+
|
|
655
|
+
# 'lab_resume' command
|
|
656
|
+
parser_lab_resume = subparsers_lab.add_parser(
|
|
657
|
+
"resume",
|
|
658
|
+
help="Resume a specific lab",
|
|
659
|
+
formatter_class=root_parser.formatter_class,
|
|
660
|
+
)
|
|
661
|
+
parser_lab_resume.set_defaults(func=lab_resume_handler)
|
|
662
|
+
|
|
663
|
+
# 'lab_paused_status' command
|
|
664
|
+
parser_lab_paused_status = subparsers_lab.add_parser(
|
|
665
|
+
"paused-status",
|
|
666
|
+
help="Show current lab paused status",
|
|
667
|
+
formatter_class=root_parser.formatter_class,
|
|
668
|
+
)
|
|
669
|
+
parser_lab_paused_status.set_defaults(func=lab_paused_status_handler)
|
|
670
|
+
parser_lab_paused_status.add_argument(
|
|
671
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
# 'lab_topology' command
|
|
675
|
+
parser_lab_topology = subparsers_lab.add_parser(
|
|
676
|
+
"topology",
|
|
677
|
+
help="Get scenario topology on current lab",
|
|
678
|
+
formatter_class=root_parser.formatter_class,
|
|
679
|
+
)
|
|
680
|
+
parser_lab_topology.set_defaults(func=lab_topology_handler)
|
|
681
|
+
parser_lab_topology.add_argument(
|
|
682
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
# 'lab_nodes' command
|
|
686
|
+
parser_lab_nodes = subparsers_lab.add_parser(
|
|
687
|
+
"nodes",
|
|
688
|
+
help="Get scenario nodes on current lab",
|
|
689
|
+
formatter_class=root_parser.formatter_class,
|
|
690
|
+
)
|
|
691
|
+
parser_lab_nodes.set_defaults(func=lab_nodes_handler)
|
|
692
|
+
parser_lab_nodes.add_argument(
|
|
693
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
# 'lab_assets' command
|
|
697
|
+
parser_lab_assets = subparsers_lab.add_parser(
|
|
698
|
+
"assets",
|
|
699
|
+
help="Get scenario assets on current lab",
|
|
700
|
+
formatter_class=root_parser.formatter_class,
|
|
701
|
+
)
|
|
702
|
+
parser_lab_assets.set_defaults(func=lab_assets_handler)
|
|
703
|
+
parser_lab_assets.add_argument(
|
|
704
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
705
|
+
)
|
|
706
|
+
|
|
707
|
+
# 'lab_attack_report' command
|
|
708
|
+
parser_lab_attack_report = subparsers_lab.add_parser(
|
|
709
|
+
"attack-report",
|
|
710
|
+
help="Get scenario attack report on current lab",
|
|
711
|
+
formatter_class=root_parser.formatter_class,
|
|
712
|
+
)
|
|
713
|
+
parser_lab_attack_report.set_defaults(func=lab_attack_report_handler)
|
|
714
|
+
parser_lab_attack_report.add_argument(
|
|
715
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
# 'lab_attack_infras' command
|
|
719
|
+
parser_lab_attack_infras = subparsers_lab.add_parser(
|
|
720
|
+
"attack-infras",
|
|
721
|
+
help="Get scenario attack infras on current lab",
|
|
722
|
+
formatter_class=root_parser.formatter_class,
|
|
723
|
+
)
|
|
724
|
+
parser_lab_attack_infras.set_defaults(func=lab_attack_infras_handler)
|
|
725
|
+
parser_lab_attack_infras.add_argument(
|
|
726
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
727
|
+
)
|
|
728
|
+
|
|
729
|
+
# 'lab_attack_sessions' command
|
|
730
|
+
parser_lab_attack_sessions = subparsers_lab.add_parser(
|
|
731
|
+
"attack-sessions",
|
|
732
|
+
help="Get scenario attack sessions on current lab",
|
|
733
|
+
formatter_class=root_parser.formatter_class,
|
|
734
|
+
)
|
|
735
|
+
parser_lab_attack_sessions.set_defaults(func=lab_attack_sessions_handler)
|
|
736
|
+
parser_lab_attack_sessions.add_argument(
|
|
737
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
# 'lab_attack_knowledge' command
|
|
741
|
+
parser_lab_attack_knowledge = subparsers_lab.add_parser(
|
|
742
|
+
"attack-knowledge",
|
|
743
|
+
help="Get scenario attack knowledge on current lab",
|
|
744
|
+
formatter_class=root_parser.formatter_class,
|
|
745
|
+
)
|
|
746
|
+
parser_lab_attack_knowledge.set_defaults(func=lab_attack_knowledge_handler)
|
|
747
|
+
parser_lab_attack_knowledge.add_argument(
|
|
748
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
749
|
+
)
|
|
750
|
+
|
|
751
|
+
# 'lab_notifications' command
|
|
752
|
+
parser_lab_notifications = subparsers_lab.add_parser(
|
|
753
|
+
"notifications",
|
|
754
|
+
help="Get scenario notifications on current lab",
|
|
755
|
+
formatter_class=root_parser.formatter_class,
|
|
756
|
+
)
|
|
757
|
+
parser_lab_notifications.set_defaults(func=lab_notifications_handler)
|
|
758
|
+
parser_lab_notifications.add_argument(
|
|
759
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
# 'lab_config' command
|
|
763
|
+
parser_lab_config = subparsers_lab.add_parser(
|
|
764
|
+
"lab-config",
|
|
765
|
+
help="Get lab config on current lab",
|
|
766
|
+
formatter_class=root_parser.formatter_class,
|
|
767
|
+
)
|
|
768
|
+
parser_lab_config.set_defaults(func=lab_config_handler)
|
|
769
|
+
parser_lab_config.add_argument(
|
|
770
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
# 'lab_get_remote_access' command
|
|
774
|
+
parser_lab_get_remote_access = subparsers_lab.add_parser(
|
|
775
|
+
"remote-access",
|
|
776
|
+
help="Get info for remote access to lab VMs",
|
|
777
|
+
)
|
|
778
|
+
parser_lab_get_remote_access.set_defaults(func=lab_get_remote_access_handler)
|
|
779
|
+
parser_lab_get_remote_access.add_argument(
|
|
780
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
# 'lab_get_security_alerts' command
|
|
784
|
+
parser_lab_get_security_alerts = subparsers_lab.add_parser(
|
|
785
|
+
"alerts",
|
|
786
|
+
help="Get triggered security alerts from supported security products (currently only MS Sentinel)",
|
|
787
|
+
)
|
|
788
|
+
parser_lab_get_security_alerts.set_defaults(func=lab_get_security_alerts_handler)
|
|
789
|
+
parser_lab_get_security_alerts.add_argument(
|
|
790
|
+
"--json", help="Return JSON result.", action="store_true"
|
|
791
|
+
)
|
|
792
|
+
|
|
793
|
+
parser_lab.set_defaults(func=lambda _: parser_lab.print_help())
|
|
794
|
+
|
|
795
|
+
# -------------------
|
|
796
|
+
# --- Redteam actions on labs
|
|
797
|
+
# -------------------
|
|
798
|
+
|
|
799
|
+
add_redteam_parser(root_parser=root_parser, subparsers=subparsers_lab)
|
|
800
|
+
|
|
801
|
+
# -------------------
|
|
802
|
+
# --- Provisioning actions on labs
|
|
803
|
+
# -------------------
|
|
804
|
+
|
|
805
|
+
add_provisioning_parser(root_parser=root_parser, subparsers=subparsers_lab)
|