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,258 @@
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
+ # 'basebox_list_handler' handler
19
+ def basebox_list_handler(args: Any) -> None:
20
+ try:
21
+ baseboxes = sorted(scenario_api.fetch_baseboxes(), key=lambda x: x.id)
22
+ except Exception as e:
23
+ print(colored(f"Error when fetching baseboxes: '{e}'", "red"))
24
+ sys.exit(1)
25
+
26
+ if args.json:
27
+ print(json.dumps(baseboxes, default=pydantic_encoder))
28
+ return
29
+
30
+ print("[+] Available baseboxes:")
31
+ for basebox in baseboxes:
32
+ print(f" [+] {basebox.id}")
33
+
34
+
35
+ #
36
+ # 'basebox_info_handler' handler
37
+ #
38
+ def basebox_info_handler(args: Any) -> None:
39
+ # Parameters
40
+ basebox_id = args.basebox_id
41
+
42
+ try:
43
+ basebox = scenario_api.fetch_basebox_by_id(basebox_id)
44
+ except Exception as e:
45
+ print(colored(f"Error when fetching basebox {basebox_id}: '{e}'", "red"))
46
+ sys.exit(1)
47
+
48
+ if args.json:
49
+ print(basebox.json())
50
+ return
51
+
52
+ print("[+] Basebox information:")
53
+ print(f" [+] \033[1mId\033[0m: {basebox.id}")
54
+ print(f" [+] \033[1mDescription\033[0m: {basebox.description}")
55
+ print(f" [+] \033[1mOS\033[0m: {basebox.operating_system} ({basebox.system_type})")
56
+ print(f" [+] \033[1mLanguage\033[0m: {basebox.language}")
57
+ print(f" [+] \033[1mInstallation date\033[0m: {basebox.installation_date}")
58
+ print(" [+] \033[1mCredentials\033[0m:")
59
+ if basebox.username != "" and basebox.username:
60
+ print(f" [+] User: {basebox.username}:{basebox.password}")
61
+ if basebox.admin_username != "" and basebox.admin_username:
62
+ print(
63
+ f" [+] Administrator: {basebox.admin_username}:{basebox.admin_password}"
64
+ )
65
+ print(" [+] \033[1mCPE\033[0m:")
66
+ for cpe in basebox.cpes:
67
+ print(f" [+] {cpe}")
68
+ if basebox.changelog:
69
+ print(" [+] \033[1mChangelog\033[0m:")
70
+ for changelog in basebox.changelog:
71
+ if changelog.get("date", "") != "":
72
+ print(f""" [+] {changelog.get("date")}:""")
73
+ for info in changelog.get("info", "").splitlines():
74
+ print(f""" {info}""")
75
+
76
+
77
+ def _basebox_create_or_run_lab(args: Any, do_run: bool = True):
78
+ # Parameters
79
+ basebox_id = args.basebox_id
80
+ lab_config_path = args.lab_config_path
81
+
82
+ if not args.workspace_id:
83
+ try:
84
+ workspace_id = get_oidc_client().get_default_workspace(raise_exc=True)
85
+ except Exception as e:
86
+ print(colored(f"Error when fetching attacks: '{e}'", "red"))
87
+ sys.exit(1)
88
+ else:
89
+ workspace_id = args.workspace_id
90
+
91
+ # Retrieve associated group id
92
+ if workspace_id is None:
93
+ print(colored("You have to specify a workspace with --workspace", "yellow"))
94
+ sys.exit(1)
95
+
96
+ # Manage lab configuration
97
+ if lab_config_path is None:
98
+ lab_config_dict = {}
99
+ else:
100
+ with open(lab_config_path, "r") as fd:
101
+ yaml_content = fd.read()
102
+ loader = YAML(typ="rt")
103
+ lab_config_dict = loader.load(yaml_content)
104
+ lab_config = LabConfig(**lab_config_dict)
105
+
106
+ # Launch basebox
107
+ try:
108
+ print(f"[+] Going to execute basebox: {basebox_id}")
109
+
110
+ if do_run:
111
+ lab_id = scenario_api.run_lab_basebox(
112
+ basebox_id,
113
+ lab_config,
114
+ workspace_id,
115
+ args.public,
116
+ )
117
+ else:
118
+ lab_id = scenario_api.create_lab_basebox(
119
+ basebox_id,
120
+ lab_config,
121
+ workspace_id,
122
+ args.public,
123
+ )
124
+
125
+ print(f"[+] Lab ID: {lab_id}")
126
+
127
+ if do_run:
128
+ wait_lab(lab_id)
129
+
130
+ except Exception as e:
131
+ print(colored(f"Error when running basebox {basebox_id}: '{e}'", "red"))
132
+ sys.exit(1)
133
+ finally:
134
+ if do_run and args.destroy_after_scenario:
135
+ print("[+] Stopping lab...")
136
+ scenario_api.stop_lab(lab_id)
137
+
138
+
139
+ #
140
+ # 'basebox_create_lab_handler' handler
141
+ #
142
+ def basebox_create_lab_handler(args: Any) -> None:
143
+ _basebox_create_or_run_lab(args, do_run=False)
144
+
145
+
146
+ #
147
+ # 'basebox_run_lab_handler' handler
148
+ #
149
+ def basebox_run_lab_handler(args: Any) -> None:
150
+ _basebox_create_or_run_lab(args, do_run=True)
151
+
152
+
153
+ def add_basebox_parser(
154
+ root_parser: argparse.ArgumentParser,
155
+ subparsers: Any,
156
+ ) -> None:
157
+ # -------------------
158
+ # --- Scenario API options (baseboxes)
159
+ # -------------------
160
+
161
+ parser_basebox = subparsers.add_parser(
162
+ "basebox",
163
+ help="Scenario API related commands (basebox)",
164
+ formatter_class=root_parser.formatter_class,
165
+ )
166
+ subparsers_basebox = parser_basebox.add_subparsers()
167
+
168
+ # 'basebox_list' command
169
+ parser_basebox_list = subparsers_basebox.add_parser(
170
+ "list",
171
+ help="List all available baseboxes",
172
+ formatter_class=root_parser.formatter_class,
173
+ )
174
+ parser_basebox_list.set_defaults(func=basebox_list_handler)
175
+ parser_basebox_list.add_argument(
176
+ "--json", help="Return JSON result.", action="store_true"
177
+ )
178
+
179
+ # 'basebox_info' command
180
+ parser_basebox_info = subparsers_basebox.add_parser(
181
+ "info",
182
+ help="Get information about a basebox",
183
+ formatter_class=root_parser.formatter_class,
184
+ )
185
+ parser_basebox_info.set_defaults(func=basebox_info_handler)
186
+ parser_basebox_info.add_argument("basebox_id", type=str, help="The basebox ID")
187
+ parser_basebox_info.add_argument(
188
+ "--json", help="Return JSON result.", action="store_true"
189
+ )
190
+
191
+ # 'basebox_create_lab' command
192
+ parser_basebox_create_lab = subparsers_basebox.add_parser(
193
+ "create",
194
+ help="Create a lab with a specific basebox",
195
+ formatter_class=root_parser.formatter_class,
196
+ )
197
+ parser_basebox_create_lab.set_defaults(func=basebox_create_lab_handler)
198
+ parser_basebox_create_lab.add_argument(
199
+ "basebox_id", type=str, help="The basebox ID"
200
+ )
201
+ parser_basebox_create_lab.add_argument(
202
+ "--destroy",
203
+ action="store_true",
204
+ dest="destroy_after_scenario",
205
+ help="Do not keep the lab up after basebox execution (False by default)",
206
+ )
207
+ parser_basebox_create_lab.add_argument(
208
+ "--workspace",
209
+ dest="workspace_id",
210
+ help="The group ID that have ownership on lab",
211
+ )
212
+ parser_basebox_create_lab.add_argument(
213
+ "--lab_config",
214
+ action="store",
215
+ required=False,
216
+ dest="lab_config_path",
217
+ help="Input path of a YAML configuration for the scenario to run",
218
+ )
219
+ parser_basebox_create_lab.add_argument(
220
+ "--public",
221
+ action="store_true",
222
+ help="Create a public link to access this lab",
223
+ )
224
+
225
+ # 'basebox_run_lab' command
226
+ parser_basebox_run_lab = subparsers_basebox.add_parser(
227
+ "run",
228
+ help="Create and run a lab with a specific basebox",
229
+ formatter_class=root_parser.formatter_class,
230
+ )
231
+ parser_basebox_run_lab.set_defaults(func=basebox_run_lab_handler)
232
+ parser_basebox_run_lab.add_argument("basebox_id", type=str, help="The basebox ID")
233
+ parser_basebox_run_lab.add_argument(
234
+ "--destroy",
235
+ action="store_true",
236
+ dest="destroy_after_scenario",
237
+ help="Do not keep the lab up after basebox execution (False by default)",
238
+ )
239
+ parser_basebox_run_lab.add_argument(
240
+ "--workspace",
241
+ dest="workspace_id",
242
+ help="The group ID that have ownership on lab",
243
+ )
244
+ parser_basebox_run_lab.add_argument(
245
+ "--lab_config",
246
+ "-lc",
247
+ action="store",
248
+ required=False,
249
+ dest="lab_config_path",
250
+ help="Input path of a YAML configuration for the scenario run",
251
+ )
252
+ parser_basebox_run_lab.add_argument(
253
+ "--public",
254
+ action="store_true",
255
+ help="Create a public link to access this lab",
256
+ )
257
+
258
+ parser_basebox.set_defaults(func=lambda _: parser_basebox.print_help())
@@ -0,0 +1,200 @@
1
+ # -*- coding: utf-8 -*-
2
+ import argparse
3
+ import json
4
+ import time
5
+ from pathlib import Path
6
+ from typing import Any
7
+ from typing import Dict
8
+ from typing import List
9
+ from typing import NoReturn
10
+ from uuid import UUID
11
+
12
+ from mantis_dataset_model.dataset_model import PartialManifest
13
+ from pydantic.json import pydantic_encoder
14
+ from rich.console import Console
15
+
16
+ from mantis_api_client import dataset_api
17
+ from mantis_api_client.exceptions import ConfigException
18
+ from mantis_api_client.oidc import get_oidc_client
19
+ from mantis_api_client.utils import colored
20
+
21
+
22
+ print = Console().print
23
+
24
+
25
+ #
26
+ # 'datasets_list_handler' handler
27
+ #
28
+ def datasets_list_handler(args: Any) -> None:
29
+ try:
30
+ datasets: Dict[str, List[Dict[UUID, PartialManifest]]] = (
31
+ dataset_api.fetch_datasets()
32
+ )
33
+ except ConfigException as e:
34
+ print("{} You should run `mantis init` again to login to M&ntis SSO.", e)
35
+ exit(1)
36
+ except Exception as e:
37
+ print(colored(f"Error when listing datasets: '{e}'", "red"))
38
+ exit(1)
39
+
40
+ if args.json:
41
+ print(json.dumps(datasets, default=pydantic_encoder))
42
+ return
43
+
44
+ print("[+] Available datasets:")
45
+ for dataset in datasets:
46
+ if len(datasets[dataset]) > 0:
47
+ print(f" [+] Datasets of '{dataset}': ")
48
+ for d in datasets[dataset]:
49
+ for id in d:
50
+ print(
51
+ f""" [+] {id} - {d[id]['name']} ({d[id]["date_dataset_created"]})"""
52
+ )
53
+
54
+
55
+ #
56
+ # 'datasets_info_handler' handler
57
+ #
58
+ def datasets_info_handler(args: Any) -> None:
59
+ # Parameters
60
+ dataset_id = args.dataset_id
61
+
62
+ try:
63
+ dataset = dataset_api.fetch_dataset_by_uuid(dataset_id)
64
+ except Exception as e:
65
+ print(colored(f"Error when fetching dataset: '{e}'", "red"))
66
+ exit(1)
67
+
68
+ if args.json:
69
+ print(dataset.json())
70
+ return
71
+
72
+ print("[+] Dataset information: ")
73
+ print(f" [+] \033[1mName\033[0m: {dataset.name}")
74
+ print(f" [+] \033[1mDescription\033[0m: {dataset.description}")
75
+ print(f" [+] \033[1mCreation date\033[0m: {dataset.date_dataset_created}")
76
+ if dataset.scenario: # scenario is optional
77
+ if (
78
+ "timestamps" in dataset.scenario
79
+ and "duration" in dataset.scenario["timestamps"]
80
+ ):
81
+ print(f" [+] Duration: {dataset.scenario['timestamps']['duration']}")
82
+
83
+
84
+ #
85
+ # 'datasets_push_handler' handler
86
+ #
87
+ def datasets_push_handler(args: Any) -> None | NoReturn:
88
+ """
89
+ Handler to upload dataset to remote
90
+
91
+ An attempt is performed to send the dataset and make it available to workspace WORKSPACE (--workspace).
92
+ """
93
+ # Parameters
94
+ dataset_path: Path = args.dataset_path
95
+ workspace_id: UUID | None = args.workspace
96
+ uploaded: bool = False
97
+ error_msg: str = ""
98
+
99
+ if not args.public:
100
+ _workspace_id: str | None = None
101
+ if not workspace_id:
102
+ _workspace_id = get_oidc_client().get_default_workspace()
103
+ if not _workspace_id:
104
+ print("[b red]No default workspace found. [/b red]Run `mantis login`.")
105
+ exit(1)
106
+ workspace_id = UUID(_workspace_id)
107
+
108
+ if dataset_path.exists() is False:
109
+ print(f"[b red]The dataset path '{dataset_path}' does not exist.")
110
+ exit(1)
111
+
112
+ if args.public:
113
+ print("[+] Uploading dataset accessible to anyone")
114
+ else:
115
+ print(f"[+] Uploading dataset accessible to workspace: {workspace_id}")
116
+
117
+ # Uploading the dataset
118
+ dataset_id = dataset_api.upload_dataset(dataset_path, workspace_id)
119
+ print(f" [+] Dataset id: {dataset_id}")
120
+
121
+ # Checking the status of the task
122
+ timeout = 300 # seconds
123
+ start = time.time()
124
+ finished = False
125
+ while time.time() - start < timeout and finished is False:
126
+ res = dataset_api.upload_dataset_status(dataset_id)
127
+ try:
128
+ state = res["status"]
129
+ error_msg = res["message"]
130
+ except KeyError:
131
+ error_msg = res.get("detail", "Unknown error")
132
+ break
133
+ if state in ["FINISHED", "FINISHED_ERROR"]:
134
+ finished = True
135
+ uploaded = state == "FINISHED"
136
+ else:
137
+ print(" [+] Dataset verification in progress")
138
+ time.sleep(1)
139
+
140
+ if uploaded is False:
141
+ print(colored(f" [-] The dataset could not be uploaded: {error_msg}", "red"))
142
+ else:
143
+ print(" [+] Successfully uploaded the dataset.")
144
+ return None
145
+
146
+
147
+ def add_dataset_parser(root_parser: argparse.ArgumentParser, subparsers: Any) -> None:
148
+
149
+ # -------------------
150
+ # --- Dataset API options
151
+ # -------------------
152
+
153
+ parser_dataset = subparsers.add_parser(
154
+ "dataset",
155
+ help="Dataset API related commands",
156
+ formatter_class=root_parser.formatter_class,
157
+ )
158
+ subparsers_dataset = parser_dataset.add_subparsers()
159
+
160
+ # 'datasets_list' command
161
+ parser_dataset_list = subparsers_dataset.add_parser(
162
+ "list",
163
+ help="List all available datasets",
164
+ formatter_class=root_parser.formatter_class,
165
+ )
166
+ parser_dataset_list.set_defaults(func=datasets_list_handler)
167
+ parser_dataset_list.add_argument(
168
+ "--json", help="Return JSON result.", action="store_true"
169
+ )
170
+
171
+ # 'datasets_info' command
172
+ parser_datasets_info = subparsers_dataset.add_parser(
173
+ "info",
174
+ help="Get information about a dataset",
175
+ formatter_class=root_parser.formatter_class,
176
+ )
177
+ parser_datasets_info.set_defaults(func=datasets_info_handler)
178
+ parser_datasets_info.add_argument("dataset_id", type=str, help="The dataset id")
179
+ parser_datasets_info.add_argument(
180
+ "--json", help="Return JSON result.", action="store_true"
181
+ )
182
+
183
+ parser_datasets_push = subparsers_dataset.add_parser(
184
+ "push",
185
+ help="Upload a local dataset .zip file to the remote",
186
+ formatter_class=root_parser.formatter_class,
187
+ )
188
+ parser_datasets_push.set_defaults(func=datasets_push_handler)
189
+ pubpriv_group = parser_datasets_push.add_mutually_exclusive_group()
190
+ pubpriv_group.add_argument(
191
+ "--workspace",
192
+ type=UUID,
193
+ metavar="WORKSPACE_UUID",
194
+ help="Workspace to which this dataset belongs",
195
+ )
196
+ pubpriv_group.add_argument("--public", action="store_true")
197
+ parser_datasets_push.add_argument(
198
+ "dataset_path", type=Path, help="Path to the dataset .zip file to upload"
199
+ )
200
+ parser_dataset.set_defaults(func=lambda _: parser_dataset.print_help())