mantis_api_client 5.3.0__tar.gz → 5.4.0__tar.gz

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.
Files changed (27) hide show
  1. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/PKG-INFO +1 -1
  2. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/cli_parser/account_parser.py +55 -13
  3. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/cli_parser/attack_parser.py +3 -3
  4. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/cli_parser/bas_parser.py +19 -6
  5. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/cli_parser/basebox_parser.py +3 -3
  6. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/cli_parser/scenario_parser.py +3 -3
  7. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/cli_parser/topology_parser.py +3 -3
  8. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/user_api.py +2 -2
  9. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/pyproject.toml +1 -1
  10. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/AUTHORS +0 -0
  11. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/LICENSE +0 -0
  12. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/README.md +0 -0
  13. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/__init__.py +0 -0
  14. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/cli_parser/dataset_parser.py +0 -0
  15. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/cli_parser/lab_parser.py +0 -0
  16. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/cli_parser/labs_parser.py +0 -0
  17. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/cli_parser/log_collector_parser.py +0 -0
  18. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/cli_parser/redteam_parser.py +0 -0
  19. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/cli_parser/signature_parser.py +0 -0
  20. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/cli_parser/user_parser.py +0 -0
  21. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/config.py +0 -0
  22. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/dataset_api.py +0 -0
  23. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/exceptions.py +0 -0
  24. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/mantis.py +0 -0
  25. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/oidc.py +0 -0
  26. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/scenario_api.py +0 -0
  27. {mantis_api_client-5.3.0 → mantis_api_client-5.4.0}/mantis_api_client/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mantis_api_client
3
- Version: 5.3.0
3
+ Version: 5.4.0
4
4
  Summary: M&NTIS Platform client API
5
5
  License: MIT
6
6
  Author: AMOSSYS
@@ -32,7 +32,7 @@ from mantis_api_client.utils import colored
32
32
  class Version:
33
33
  def __init__(self, str_vers: str) -> None:
34
34
  try:
35
- self.major, self.minor, self.patch = str_vers.split(".")
35
+ self.major, self.minor, self.patch = str_vers.split(".", 2)
36
36
  except Exception as e:
37
37
  raise Exception(
38
38
  "Bad version format for '{}': 'X.Y.Z' expected. Error: {}".format(
@@ -123,6 +123,31 @@ def status_handler(args: Any) -> None: # noqa: C901
123
123
  )
124
124
  )
125
125
 
126
+ # User API
127
+ print(" [+] Backoffice API")
128
+ print(" [+] address: {}".format(mantis_api_client_config.user_api_url))
129
+ try:
130
+ user_api_version = user_api.get_version()
131
+ user_vers = Version(str_vers=user_api_version)
132
+ except requests.exceptions.ConnectionError:
133
+ exit_code = 1
134
+ print(" [-] API status: " + colored("not running !", "white", "on_red"))
135
+ else:
136
+ print(" [+] API status: " + colored("OK", "grey", "on_green"))
137
+ print(" [+] version: {}".format(user_api_version))
138
+ if user_vers.major != client_vers.major:
139
+ exit_code = 1
140
+ print(
141
+ " [-] "
142
+ + colored(
143
+ "Error: User API major version ({}) mismatchs with mantis_api_client major version ({})".format(
144
+ user_vers.major, client_vers.major
145
+ ),
146
+ "white",
147
+ "on_red",
148
+ )
149
+ )
150
+
126
151
  if exit_code != 0:
127
152
  sys.exit(exit_code)
128
153
 
@@ -144,25 +169,42 @@ def info_handler(args: Any) -> None: # noqa: C901
144
169
  access_token = jwt.get_unverified_claims(active_tokens["access_token"])
145
170
  id_token = jwt.get_unverified_claims(active_tokens["id_token"])
146
171
 
147
- user_groups = id_token.get("groups")
148
-
172
+ sub_orgs = user_api.fetch_current_seats()
173
+ sub_membership: dict[str, list[dict]] = {}
174
+ sub_default_ws = get_oidc_client().get_default_workspace()
149
175
  # Handle case where the user pertains to an organization
150
- if user_groups is not None:
151
- default_group = get_oidc_client().get_default_workspace()
152
- groups_comma_sep = ", ".join(user_groups)
153
- if default_group:
154
- # mark default group bold
155
- groups_comma_sep = groups_comma_sep.replace(
156
- default_group, f"[bold]{default_group}[/bold]"
157
- )
176
+ if sub_orgs:
177
+ sub_wss = user_api.fetch_current_workspaces()
178
+ for sub_org in sub_orgs:
179
+ sub_org_wss: list[dict] = []
180
+ sub_membership[sub_org["name"]] = sub_org_wss
181
+ for sub_ws in sub_wss:
182
+ if sub_org["id"] == sub_ws["organization_id"]:
183
+ sub_org_wss.append(sub_ws)
158
184
 
159
185
  sorted_scopes = ", ".join(sorted(access_token["scope"].split()))
160
186
 
161
187
  print(f"[+] Connected to {active_profile_domain}")
162
188
  print(f" [+] Username: {id_token['preferred_username']}")
163
189
  print(f" [+] Email: {id_token.get('email', 'N/A')}")
164
- if user_groups is not None:
165
- rprint(rf" \[+] Group membership: {groups_comma_sep}")
190
+ if sub_orgs:
191
+
192
+ def hl_activated_ws(ws: dict) -> str:
193
+ if ws["id"] == sub_default_ws:
194
+ return f"[bold]{ws['name']}[/bold]"
195
+ return ws["name"]
196
+
197
+ rprint(
198
+ r" \[+] Organization membership: {}".format(
199
+ ", ".join(
200
+ "{} ({})".format(
201
+ org,
202
+ ", ".join(map(hl_activated_ws, org_wss)),
203
+ )
204
+ for org, org_wss in sub_membership.items()
205
+ )
206
+ )
207
+ )
166
208
  print(f" [+] Scopes: {sorted_scopes}")
167
209
 
168
210
 
@@ -95,7 +95,7 @@ def _attack_create_or_run_lab(args: Any, do_run: bool = True):
95
95
 
96
96
  # Retrieve associated group id
97
97
  if workspace_id is None:
98
- print(colored("Your subscription level does not permit to launch labs", "red"))
98
+ print(colored("You have to specify a workspace with --workspace", "yellow"))
99
99
  sys.exit(1)
100
100
 
101
101
  # Safety checks
@@ -246,7 +246,7 @@ def add_attack_parser(root_parser: argparse.ArgumentParser, subparsers: Any) ->
246
246
  help="Allows to define the unit scenario to create_lab for a unit attack",
247
247
  )
248
248
  parser_attack_create_lab.add_argument(
249
- "--workspace_id",
249
+ "--workspace",
250
250
  dest="workspace_id",
251
251
  help="The group name that have ownership on lab",
252
252
  )
@@ -280,7 +280,7 @@ def add_attack_parser(root_parser: argparse.ArgumentParser, subparsers: Any) ->
280
280
  help="Allows to define the unit scenario to run for a unit attack",
281
281
  )
282
282
  parser_attack_run_lab.add_argument(
283
- "--workspace_id",
283
+ "--workspace",
284
284
  dest="workspace_id",
285
285
  help="The group name that have ownership on lab",
286
286
  )
@@ -13,7 +13,6 @@ from mantis_scenario_model.api_scenario_model import LabListReply
13
13
  from mantis_scenario_model.lab_config_model import ContentType
14
14
  from mantis_scenario_model.lab_config_model import LabConfig
15
15
  from mantis_scenario_model.lab_model import Lab
16
- from mantis_scenario_model.lab_model import ScenarioExecutionStatus
17
16
 
18
17
  from cr_api_client import redteam_api
19
18
  from mantis_api_client import scenario_api
@@ -113,7 +112,6 @@ def bas_create_handler(args: Any) -> None:
113
112
  wait_lab(
114
113
  lab_id,
115
114
  quiet=True,
116
- exit_on_status=[ScenarioExecutionStatus.scenario_execution],
117
115
  )
118
116
 
119
117
  except Exception as e:
@@ -129,7 +127,7 @@ def bas_create_handler(args: Any) -> None:
129
127
  unset_current_lab()
130
128
 
131
129
  print(
132
- f"[hint] You can retrieve BAS agent download URLs with: '$ mantis bas info {lab_id}'"
130
+ f"[hint] Once an agent has been executed, retrieve its session with: '$ mantis bas info {lab_id}'"
133
131
  )
134
132
 
135
133
 
@@ -239,7 +237,7 @@ def show_available_attacks(session_id: str, filter_tactic: str, filter_technique
239
237
  for attack in attacks:
240
238
 
241
239
  # Check if the attack is runnable
242
- if attack["status"] != "runnable":
240
+ if attack["status"] not in ["runnable", "failed"]:
243
241
  continue
244
242
 
245
243
  # Check if the attack is BAS compatible
@@ -308,8 +306,14 @@ def show_available_attacks(session_id: str, filter_tactic: str, filter_technique
308
306
  )
309
307
  mitre_attack_infos = f"{technique_id} [{mitre_attack_tactics}]"
310
308
 
309
+ # Retrieve potential attack parameters that user can control
310
+ parameters = ""
311
+ attack_info = scenario_api.fetch_attack_by_name(attack['worker']['name'])
312
+ if attack_info.options:
313
+ parameters = f" - \033[1mparameters\033[0m: {', '.join(attack_info.options)}"
314
+
311
315
  print(
312
- f" - \033[1mattack_name\033[0m: {attack['worker']['name']:<30} - \033[1mATT&CK\033[0m: {mitre_attack_infos:<18} - \033[1mdescription\033[0m: {attack['worker']['title']} - \033[1mattack_parameters\033[0m:{attack_values}"
316
+ f" - \033[1mname\033[0m: {attack['worker']['name']:<30} - \033[1mATT&CK\033[0m: {mitre_attack_infos:<18} - \033[1mdescription\033[0m: {attack['worker']['title']:<30} - \033[1mvalues\033[0m:{attack_values}{parameters}"
313
317
  )
314
318
 
315
319
 
@@ -523,6 +527,15 @@ def bas_attack_run_handler(args: Any) -> None:
523
527
 
524
528
  params = parse_vars(params)
525
529
 
530
+ # Retrieve potential attack parameters that user can control, and
531
+ # check that the attack accepts those parameters
532
+ attack_info = scenario_api.fetch_attack_by_name(attack_name)
533
+
534
+ for key in params.keys():
535
+ if key not in attack_info.options:
536
+ print(colored(f"Parameter '{key}' is not in the supported parameter list. Supported parameters are: {', '.join(attack_info.options)}", "red"))
537
+ sys.exit(1)
538
+
526
539
  # Retrieve matching bas lab
527
540
  lab_id = find_bas_lab_from_session_id(session_id)
528
541
 
@@ -687,7 +700,7 @@ def add_bas_parser(root_parser: argparse.ArgumentParser, subparsers: Any) -> Non
687
700
  )
688
701
  parser_bas_create.set_defaults(func=bas_create_handler)
689
702
  parser_bas_create.add_argument(
690
- "--workspace_id",
703
+ "--workspace",
691
704
  dest="workspace_id",
692
705
  help="The workspace ID on which you want to create the BAS lab infrastructure",
693
706
  )
@@ -90,7 +90,7 @@ def _basebox_create_or_run_lab(args: Any, do_run: bool = True):
90
90
 
91
91
  # Retrieve associated group id
92
92
  if workspace_id is None:
93
- print(colored("Your subscription level does not permit to launch labs", "red"))
93
+ print(colored("You have to specify a workspace with --workspace", "yellow"))
94
94
  sys.exit(1)
95
95
 
96
96
  # Manage lab configuration
@@ -203,7 +203,7 @@ def add_basebox_parser(
203
203
  help="Do not keep the lab up after basebox execution (False by default)",
204
204
  )
205
205
  parser_basebox_create_lab.add_argument(
206
- "--workspace_id",
206
+ "--workspace",
207
207
  dest="workspace_id",
208
208
  help="The group ID that have ownership on lab",
209
209
  )
@@ -230,7 +230,7 @@ def add_basebox_parser(
230
230
  help="Do not keep the lab up after basebox execution (False by default)",
231
231
  )
232
232
  parser_basebox_run_lab.add_argument(
233
- "--workspace_id",
233
+ "--workspace",
234
234
  dest="workspace_id",
235
235
  help="The group ID that have ownership on lab",
236
236
  )
@@ -95,7 +95,7 @@ def _scenario_create_or_run_lab(args: Any, do_run: bool = True):
95
95
 
96
96
  # Retrieve associated group id
97
97
  if workspace_id is None:
98
- print(colored("Your subscription level does not permit to launch labs", "red"))
98
+ print(colored("You have to specify a workspace with --workspace", "yellow"))
99
99
  sys.exit(1)
100
100
 
101
101
  # Manage lab configuration
@@ -226,7 +226,7 @@ def add_scenario_parser(
226
226
  "scenario_name", type=str, help="The scenario name"
227
227
  )
228
228
  parser_scenario_create_lab.add_argument(
229
- "--workspace_id",
229
+ "--workspace",
230
230
  dest="workspace_id",
231
231
  help="The group ID that have ownership on lab",
232
232
  )
@@ -263,7 +263,7 @@ def add_scenario_parser(
263
263
  "scenario_name", type=str, help="The scenario name"
264
264
  )
265
265
  parser_scenario_run_lab.add_argument(
266
- "--workspace_id",
266
+ "--workspace",
267
267
  dest="workspace_id",
268
268
  help="The group ID that have ownership on lab",
269
269
  )
@@ -82,7 +82,7 @@ def _topology_create_or_run_lab(args: Any, do_run: bool = True):
82
82
 
83
83
  # Retrieve associated group id
84
84
  if workspace_id is None:
85
- print(colored("Your subscription level does not permit to launch labs", "red"))
85
+ print(colored("You have to specify a workspace with --workspace", "yellow"))
86
86
  sys.exit(1)
87
87
 
88
88
  # Manage lab configuration
@@ -198,7 +198,7 @@ def add_topology_parser(
198
198
  help="Do not keep the lab up after topology execution (False by default)",
199
199
  )
200
200
  parser_topology_create_lab.add_argument(
201
- "--workspace_id",
201
+ "--workspace",
202
202
  dest="workspace_id",
203
203
  help="The group ID that have ownership on lab",
204
204
  )
@@ -227,7 +227,7 @@ def add_topology_parser(
227
227
  help="Do not keep the lab up after topology execution (False by default)",
228
228
  )
229
229
  parser_topology_run_lab.add_argument(
230
- "--workspace_id",
230
+ "--workspace",
231
231
  dest="workspace_id",
232
232
  help="The group ID that have ownership on lab",
233
233
  )
@@ -75,14 +75,14 @@ def _handle_error(
75
75
 
76
76
  def get_version() -> str:
77
77
  """
78
- Return publish API version.
78
+ Return User API version.
79
79
 
80
80
  :return: The version number is a string
81
81
  """
82
82
  result = authorize(_get)("/version")
83
83
 
84
84
  if result.status_code != 200:
85
- _handle_error(result, "Cannot retrieve scenario API version")
85
+ _handle_error(result, "Cannot retrieve User API version")
86
86
 
87
87
  return result.json()
88
88
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "mantis_api_client"
3
- version = "5.3.0"
3
+ version = "5.4.0"
4
4
  description = "M&NTIS Platform client API"
5
5
  readme = "README.md"
6
6
  authors = ["AMOSSYS"]