regscale-cli 6.24.0.0__py3-none-any.whl → 6.25.0.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.

Potentially problematic release.


This version of regscale-cli might be problematic. Click here for more details.

Files changed (32) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/api.py +1 -1
  3. regscale/core/app/application.py +5 -3
  4. regscale/core/app/internal/evidence.py +308 -202
  5. regscale/dev/code_gen.py +84 -3
  6. regscale/integrations/commercial/__init__.py +2 -0
  7. regscale/integrations/commercial/jira.py +95 -22
  8. regscale/integrations/commercial/microsoft_defender/defender.py +326 -5
  9. regscale/integrations/commercial/microsoft_defender/defender_api.py +348 -14
  10. regscale/integrations/commercial/microsoft_defender/defender_constants.py +157 -0
  11. regscale/integrations/commercial/synqly/assets.py +99 -16
  12. regscale/integrations/commercial/synqly/query_builder.py +533 -0
  13. regscale/integrations/commercial/synqly/vulnerabilities.py +134 -14
  14. regscale/integrations/commercial/wizv2/click.py +23 -0
  15. regscale/integrations/commercial/wizv2/compliance_report.py +137 -26
  16. regscale/integrations/compliance_integration.py +247 -5
  17. regscale/integrations/scanner_integration.py +16 -0
  18. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  19. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +12 -2
  20. regscale/models/integration_models/synqly_models/filter_parser.py +332 -0
  21. regscale/models/integration_models/synqly_models/synqly_model.py +47 -3
  22. regscale/models/regscale_models/compliance_settings.py +28 -0
  23. regscale/models/regscale_models/component.py +1 -0
  24. regscale/models/regscale_models/control_implementation.py +143 -4
  25. regscale/regscale.py +1 -1
  26. regscale/validation/record.py +23 -1
  27. {regscale_cli-6.24.0.0.dist-info → regscale_cli-6.25.0.0.dist-info}/METADATA +9 -9
  28. {regscale_cli-6.24.0.0.dist-info → regscale_cli-6.25.0.0.dist-info}/RECORD +32 -30
  29. {regscale_cli-6.24.0.0.dist-info → regscale_cli-6.25.0.0.dist-info}/LICENSE +0 -0
  30. {regscale_cli-6.24.0.0.dist-info → regscale_cli-6.25.0.0.dist-info}/WHEEL +0 -0
  31. {regscale_cli-6.24.0.0.dist-info → regscale_cli-6.25.0.0.dist-info}/entry_points.txt +0 -0
  32. {regscale_cli-6.24.0.0.dist-info → regscale_cli-6.25.0.0.dist-info}/top_level.txt +0 -0
@@ -2,6 +2,7 @@
2
2
  # -*- coding: utf-8 -*-
3
3
  """RegScale Microsoft Defender recommendations and alerts integration"""
4
4
  # standard python imports
5
+
5
6
  import logging
6
7
  from datetime import datetime, timedelta
7
8
  from os import PathLike
@@ -10,6 +11,7 @@ from typing import Literal, Optional, Tuple, Union
10
11
 
11
12
  import click
12
13
  from rich.console import Console
14
+ from rich.table import Table
13
15
 
14
16
  from regscale.core.app.api import Api
15
17
  from regscale.core.app.internal.login import is_valid
@@ -24,6 +26,7 @@ from regscale.core.app.utils.app_utils import (
24
26
  uncamel_case,
25
27
  )
26
28
  from regscale.integrations.commercial.microsoft_defender.defender_api import DefenderApi
29
+ from regscale.integrations.commercial.microsoft_defender.defender_constants import EVIDENCE_TO_CONTROLS_MAPPING
27
30
  from regscale.models import File, Issue, regscale_id, regscale_module, ssp_or_component_id
28
31
  from regscale.models.app_models.click import NotRequiredIf
29
32
  from regscale.models.integration_models.defender_data import DefenderData
@@ -68,12 +71,12 @@ def defender():
68
71
  @defender.command(name="authenticate")
69
72
  @click.option(
70
73
  "--system",
71
- type=click.Choice(["cloud", "365"], case_sensitive=False),
72
- help="Pull recommendations from Microsoft Defender 365 or Microsoft Defender for Cloud.",
74
+ type=click.Choice(["cloud", "365", "entra"], case_sensitive=False),
75
+ help="Pull recommendations from Microsoft Defender 365, Microsoft Defender for Cloud, or Azure Entra.",
73
76
  prompt="Please choose a system",
74
77
  required=True,
75
78
  )
76
- def authenticate_in_defender(system: Literal["cloud", "365"]):
79
+ def authenticate_in_defender(system: Literal["cloud", "365", "entra"]):
77
80
  """Obtains an access token using the credentials provided in init.yaml."""
78
81
  authenticate(system=system)
79
82
 
@@ -225,6 +228,85 @@ def import_alerts(
225
228
  )
226
229
 
227
230
 
231
+ @defender.command(name="collect_entra_evidence")
232
+ @ssp_or_component_id()
233
+ @click.option(
234
+ "--days_back",
235
+ "-d",
236
+ type=click.INT,
237
+ help="Number of days back to collect audit logs",
238
+ default=30,
239
+ )
240
+ @click.option(
241
+ "--evidence_type",
242
+ "-t",
243
+ type=click.Choice(
244
+ ["all", "users_groups", "rbac_pim", "conditional_access", "authentication", "audit_logs", "access_reviews"],
245
+ case_sensitive=False,
246
+ ),
247
+ help="Type of evidence to collect",
248
+ default="all",
249
+ )
250
+ def collect_entra_evidence(regscale_ssp_id: int, component_id: int, days_back: int, evidence_type: str):
251
+ """
252
+ Collect Azure Entra evidence for FedRAMP compliance controls and upload to RegScale
253
+ """
254
+ # Validate parent module for evidence collection
255
+ from regscale.validation.record import validate_component_or_ssp
256
+
257
+ validate_component_or_ssp(ssp_id=regscale_ssp_id, component_id=component_id)
258
+ parent_id = regscale_ssp_id or component_id
259
+ parent_module = "securityplans" if regscale_ssp_id else "components"
260
+
261
+ collect_and_upload_entra_evidence(
262
+ parent_id=parent_id, parent_module=parent_module, days_back=days_back, evidence_type=evidence_type
263
+ )
264
+
265
+
266
+ @defender.command(name="show_entra_mappings")
267
+ @click.option(
268
+ "--evidence_type",
269
+ "-t",
270
+ type=click.Choice(
271
+ ["all", "users_groups", "rbac_pim", "conditional_access", "authentication", "audit_logs", "access_reviews"],
272
+ case_sensitive=False,
273
+ ),
274
+ help="Show mappings for specific evidence type",
275
+ default="all",
276
+ )
277
+ def show_entra_mappings(evidence_type: str):
278
+ """
279
+ Show which FedRAMP controls are mapped to each Azure Entra evidence type
280
+ """
281
+ if evidence_type == "all":
282
+ evidence_types_to_show = EVIDENCE_TO_CONTROLS_MAPPING.keys()
283
+ else:
284
+ # Map category to specific evidence types
285
+ category_to_evidence = {
286
+ "users_groups": ["users", "guest_users", "security_groups"],
287
+ "rbac_pim": ["role_assignments", "role_definitions", "pim_assignments", "pim_eligibility"],
288
+ "conditional_access": ["conditional_access"],
289
+ "authentication": ["auth_methods_policy", "user_mfa_registration", "mfa_registered_users"],
290
+ "audit_logs": ["sign_in_logs", "directory_audits", "provisioning_logs"],
291
+ "access_reviews": ["access_review_definitions"],
292
+ }
293
+ evidence_types_to_show = category_to_evidence.get(evidence_type, [evidence_type])
294
+ # create a table using rich and add a row for each evidence type
295
+ table = Table(title="Azure Entra Evidence to FedRAMP Controls Mapping", show_lines=True)
296
+ table.add_column("Evidence Type", style="#10c4d3")
297
+ table.add_column("Controls", style="#18a8e9")
298
+ table.add_column("Total Controls", style="#ff9d20")
299
+ for evidence_key in evidence_types_to_show:
300
+ if evidence_key in EVIDENCE_TO_CONTROLS_MAPPING:
301
+ controls = EVIDENCE_TO_CONTROLS_MAPPING[evidence_key]
302
+ table.add_row(evidence_key.replace("_", " ").title(), ", ".join(controls), str(len(controls)))
303
+ console.print(table)
304
+
305
+ console.print(
306
+ "[dim]Use 'regscale defender collect_entra_evidence' to collect and upload evidence to these controls[/dim]"
307
+ )
308
+
309
+
228
310
  def import_defender_alerts(
229
311
  folder_path: PathLike[str],
230
312
  regscale_ssp_id: int,
@@ -268,11 +350,11 @@ def import_defender_alerts(
268
350
  )
269
351
 
270
352
 
271
- def authenticate(system: Literal["cloud", "365"]) -> None:
353
+ def authenticate(system: Literal["cloud", "365", "entra"]) -> None:
272
354
  """
273
355
  Obtains an access token using the credentials provided in init.yaml
274
356
 
275
- :param Literal["cloud", "365"] system: The system to authenticate for, either Defender 365 or Defender for Cloud
357
+ :param Literal["cloud", "365", "entra"] system: The system to authenticate for, either Defender 365, Defender for Cloud, or Azure Entra
276
358
  :rtype: None
277
359
  """
278
360
  _ = check_license()
@@ -947,3 +1029,242 @@ def fetch_save_and_upload_query(
947
1029
  api=defender_api.api,
948
1030
  ):
949
1031
  logger.info(f"Successfully uploaded {file_path.name} to {parent_module} #{parent_id} in RegScale.")
1032
+
1033
+
1034
+ def collect_and_upload_entra_evidence(
1035
+ parent_id: int, parent_module: str, days_back: int = 30, evidence_type: str = "all"
1036
+ ) -> None:
1037
+ """
1038
+ Collect Azure Entra evidence for FedRAMP compliance controls and upload to RegScale
1039
+
1040
+ :param int parent_id: The RegScale ID to upload evidence to
1041
+ :param str parent_module: The RegScale module to upload evidence to
1042
+ :param int days_back: Number of days back to collect audit logs
1043
+ :param str evidence_type: Type of evidence to collect
1044
+ :rtype: None
1045
+ """
1046
+ app = check_license()
1047
+ api = Api()
1048
+
1049
+ if not is_valid(app=app):
1050
+ error_and_exit(LOGIN_ERROR)
1051
+
1052
+ logger.info(f"Starting Azure Entra evidence collection for {evidence_type}...")
1053
+
1054
+ defender_api = DefenderApi(system="entra")
1055
+
1056
+ try:
1057
+ if evidence_type == "all":
1058
+ evidence_data = defender_api.collect_all_entra_evidence(days_back=days_back)
1059
+ else:
1060
+ evidence_data = collect_specific_evidence_type(defender_api, evidence_type, days_back)
1061
+
1062
+ upload_evidence_files(evidence_data, parent_id, parent_module, api, evidence_type)
1063
+
1064
+ except Exception as e:
1065
+ error_and_exit(f"Error collecting Azure Entra evidence: {e}")
1066
+
1067
+
1068
+ def collect_specific_evidence_type(
1069
+ defender_api: DefenderApi, evidence_type: str, days_back: int
1070
+ ) -> dict[str, list[Path]]:
1071
+ """
1072
+ Collect specific type of Azure Entra evidence
1073
+
1074
+ :param DefenderApi defender_api: The Defender API instance
1075
+ :param str evidence_type: Type of evidence to collect
1076
+ :param int days_back: Number of days back for audit logs
1077
+ :return: Dictionary containing evidence data and file paths to saved csv evidence files
1078
+ :rtype: dict[str, list[Path]]
1079
+ """
1080
+ evidence_data = {}
1081
+ start_date = (datetime.now() - timedelta(days=days_back)).strftime("%Y-%m-%dT00:00:00Z")
1082
+
1083
+ if evidence_type == "users_groups":
1084
+ evidence_data["users"] = defender_api.get_and_save_entra_evidence("users")
1085
+ evidence_data["guest_users"] = defender_api.get_and_save_entra_evidence("guest_users")
1086
+ evidence_data["groups_and_members"] = defender_api.get_and_save_entra_evidence("groups_and_members")
1087
+ evidence_data["security_groups"] = defender_api.get_and_save_entra_evidence("security_groups")
1088
+
1089
+ elif evidence_type == "rbac_pim":
1090
+ evidence_data["role_assignments"] = defender_api.get_and_save_entra_evidence("role_assignments")
1091
+ evidence_data["role_definitions"] = defender_api.get_and_save_entra_evidence("role_definitions")
1092
+ evidence_data["pim_assignments"] = defender_api.get_and_save_entra_evidence("pim_assignments")
1093
+ evidence_data["pim_eligibility"] = defender_api.get_and_save_entra_evidence("pim_eligibility")
1094
+
1095
+ elif evidence_type == "conditional_access":
1096
+ evidence_data["conditional_access"] = defender_api.get_and_save_entra_evidence("conditional_access")
1097
+
1098
+ elif evidence_type == "authentication":
1099
+ evidence_data["auth_methods_policy"] = defender_api.get_and_save_entra_evidence("auth_methods_policy")
1100
+ evidence_data["user_mfa_registration"] = defender_api.get_and_save_entra_evidence("user_mfa_registration")
1101
+ evidence_data["mfa_registered_users"] = defender_api.get_and_save_entra_evidence("mfa_registered_users")
1102
+
1103
+ elif evidence_type == "audit_logs":
1104
+ evidence_data["sign_in_logs"] = defender_api.get_and_save_entra_evidence("sign_in_logs", start_date=start_date)
1105
+ evidence_data["directory_audits"] = defender_api.get_and_save_entra_evidence(
1106
+ "directory_audits", start_date=start_date
1107
+ )
1108
+ evidence_data["provisioning_logs"] = defender_api.get_and_save_entra_evidence(
1109
+ "provisioning_logs", start_date=start_date
1110
+ )
1111
+
1112
+ elif evidence_type == "access_reviews":
1113
+ evidence_data["access_review_definitions"] = defender_api.collect_entra_access_reviews()
1114
+
1115
+ return evidence_data
1116
+
1117
+
1118
+ def get_control_implementations_map(parent_id: int, parent_module: str) -> dict[str, int]:
1119
+ """
1120
+ Get a mapping of control identifiers to control implementation IDs
1121
+
1122
+ :param int parent_id: RegScale parent ID
1123
+ :param str parent_module: RegScale parent module
1124
+ :return: Dictionary mapping control identifiers (e.g., "AC-2") to control implementation IDs
1125
+ :rtype: dict[str, int]
1126
+ """
1127
+ from regscale.models import ControlImplementation
1128
+
1129
+ try:
1130
+ control_implementations = ControlImplementation.get_list_by_parent(parent_id, parent_module)
1131
+ if not control_implementations:
1132
+ logger.warning(f"No control implementations found for {parent_module} #{parent_id}")
1133
+ return {}
1134
+
1135
+ control_map = {}
1136
+ for control_impl in control_implementations:
1137
+ # Try to get control identifier from the control object
1138
+ control_id = control_impl.get("controlId") if isinstance(control_impl, dict) else control_impl.controlId
1139
+ id_number = control_impl.get("id") if isinstance(control_impl, dict) else control_impl.id
1140
+ control_map[control_id] = id_number
1141
+ logger.debug(f"Mapped control #{id_number}: {control_id} to implementation.")
1142
+
1143
+ logger.info(f"Found {len(control_map)} control implementations for evidence mapping")
1144
+ return control_map
1145
+
1146
+ except Exception as e:
1147
+ logger.error(f"Error fetching control implementations: {e}")
1148
+ return {}
1149
+
1150
+
1151
+ def upload_evidence_to_controls(
1152
+ evidence_key: str,
1153
+ evidence_file_list: list[Path],
1154
+ control_implementations_map: dict[str, int],
1155
+ api: Api,
1156
+ ) -> int:
1157
+ """
1158
+ Upload evidence file to specific control implementations
1159
+
1160
+ :param str evidence_key: Type of evidence (e.g., "users", "sign_in_logs")
1161
+ :param list evidence_file_list: List of evidence files
1162
+ :param dict control_implementations_map: Map of control identifiers to implementation IDs
1163
+ :param Api api: API instance
1164
+ :return: Number of successful uploads
1165
+ :rtype: int
1166
+ """
1167
+ # Get the controls this evidence type maps to
1168
+ mapped_controls = EVIDENCE_TO_CONTROLS_MAPPING.get(evidence_key, [])
1169
+ if not mapped_controls:
1170
+ logger.warning(f"No control mapping found for evidence type: {evidence_key}")
1171
+ return 0
1172
+
1173
+ # Write evidence data to CSV file
1174
+ successful_uploads = 0
1175
+ for file_path in evidence_file_list:
1176
+ controls_uploaded_to = []
1177
+ file_name = file_path.name
1178
+
1179
+ for control_identifier in mapped_controls:
1180
+ if control_identifier in control_implementations_map:
1181
+ control_impl_id = control_implementations_map[control_identifier]
1182
+
1183
+ # Upload file to specific control implementation
1184
+ if File.upload_file_to_regscale(
1185
+ file_name=file_path.absolute(), # type: ignore
1186
+ parent_id=control_impl_id,
1187
+ parent_module="controls",
1188
+ api=api,
1189
+ ):
1190
+ successful_uploads += 1
1191
+ controls_uploaded_to.append(control_identifier)
1192
+ logger.debug(
1193
+ f"Successfully uploaded {file_name} to control {control_identifier} (ID: {control_impl_id})"
1194
+ )
1195
+ else:
1196
+ logger.error(
1197
+ f"Failed to upload {file_name} to control {control_identifier} (ID: {control_impl_id})"
1198
+ )
1199
+
1200
+ if controls_uploaded_to:
1201
+ logger.info(
1202
+ f"Successfully uploaded {file_name} to {len(controls_uploaded_to)} controls: {', '.join(controls_uploaded_to)}"
1203
+ )
1204
+ else:
1205
+ logger.warning(f"No matching control implementations found for {evidence_key} evidence")
1206
+
1207
+ return successful_uploads
1208
+
1209
+
1210
+ def upload_evidence_files(
1211
+ evidence_data: dict[str, list[Path]], parent_id: int, parent_module: str, api: Api, evidence_type: str
1212
+ ) -> None:
1213
+ """
1214
+ Upload evidence files to specific RegScale control implementations
1215
+
1216
+ :param dict[str, list[Path]] evidence_data: Dictionary containing evidence data
1217
+ :param int parent_id: RegScale parent ID
1218
+ :param str parent_module: RegScale parent module
1219
+ :param Api api: API instance
1220
+ :param str evidence_type: Type of evidence collected
1221
+ :rtype: None
1222
+ """
1223
+ from regscale.integrations.commercial.microsoft_defender.defender_constants import EVIDENCE_CATEGORIES
1224
+
1225
+ artifacts_dir = Path("./artifacts")
1226
+ artifacts_dir.mkdir(exist_ok=True)
1227
+
1228
+ # Get control implementations mapping for evidence targeting
1229
+ control_implementations_map = get_control_implementations_map(parent_id, parent_module)
1230
+
1231
+ if not control_implementations_map:
1232
+ logger.error(
1233
+ f"No control implementations found for {parent_module} #{parent_id}. Cannot map evidence to controls."
1234
+ )
1235
+ return
1236
+
1237
+ total_successful_uploads = 0
1238
+ total_evidence_items = 0
1239
+ evidence_summary = []
1240
+
1241
+ for evidence_key, evidence_list in evidence_data.items():
1242
+ if not evidence_list:
1243
+ logger.warning(f"No data found for {evidence_key}")
1244
+ continue
1245
+
1246
+ total_evidence_items += len(evidence_list)
1247
+
1248
+ # Upload evidence to specific control implementations
1249
+ uploads_for_evidence = upload_evidence_to_controls(
1250
+ evidence_key=evidence_key,
1251
+ evidence_file_list=evidence_list,
1252
+ control_implementations_map=control_implementations_map,
1253
+ api=api,
1254
+ )
1255
+
1256
+ total_successful_uploads += uploads_for_evidence
1257
+ evidence_summary.append(f"{evidence_key}: {len(evidence_list)} items → {uploads_for_evidence} control uploads")
1258
+
1259
+ # Summary
1260
+ category_name = EVIDENCE_CATEGORIES.get(evidence_type, f"Azure Entra {evidence_type.replace('_', ' ').title()}")
1261
+ logger.info(
1262
+ f"Azure Entra evidence collection complete for {category_name}. "
1263
+ f"Collected {total_evidence_items} total items across {total_successful_uploads} control-specific uploads."
1264
+ )
1265
+
1266
+ # Detailed summary
1267
+ if evidence_summary:
1268
+ logger.info("Evidence upload summary:")
1269
+ for summary in evidence_summary:
1270
+ logger.info(f" - {summary}")