regscale-cli 6.25.1.0__py3-none-any.whl → 6.26.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 (80) hide show
  1. regscale/_version.py +1 -1
  2. regscale/airflow/hierarchy.py +2 -2
  3. regscale/core/app/application.py +18 -3
  4. regscale/core/app/internal/login.py +0 -1
  5. regscale/core/app/utils/catalog_utils/common.py +1 -1
  6. regscale/integrations/commercial/sicura/api.py +14 -13
  7. regscale/integrations/commercial/sicura/commands.py +8 -2
  8. regscale/integrations/commercial/sicura/scanner.py +49 -39
  9. regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
  10. regscale/integrations/commercial/wizv2/click.py +26 -26
  11. regscale/integrations/commercial/wizv2/compliance_report.py +152 -157
  12. regscale/integrations/commercial/wizv2/scanner.py +3 -3
  13. regscale/integrations/compliance_integration.py +67 -2
  14. regscale/integrations/control_matcher.py +358 -0
  15. regscale/integrations/milestone_manager.py +291 -0
  16. regscale/integrations/public/__init__.py +1 -0
  17. regscale/integrations/public/cci_importer.py +37 -38
  18. regscale/integrations/public/fedramp/click.py +60 -2
  19. regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
  20. regscale/integrations/scanner_integration.py +150 -96
  21. regscale/models/integration_models/cisa_kev_data.json +154 -4
  22. regscale/models/integration_models/nexpose.py +36 -10
  23. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  24. regscale/models/locking.py +12 -8
  25. regscale/models/platform.py +1 -2
  26. regscale/models/regscale_models/control_implementation.py +46 -21
  27. regscale/models/regscale_models/issue.py +256 -94
  28. regscale/models/regscale_models/milestone.py +1 -1
  29. regscale/models/regscale_models/regscale_model.py +6 -1
  30. regscale/templates/__init__.py +0 -0
  31. {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.26.0.0.dist-info}/METADATA +1 -1
  32. {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.26.0.0.dist-info}/RECORD +80 -33
  33. tests/regscale/integrations/commercial/__init__.py +0 -0
  34. tests/regscale/integrations/commercial/conftest.py +28 -0
  35. tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
  36. tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
  37. tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
  38. tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
  39. tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
  40. tests/regscale/integrations/commercial/test_aws.py +3731 -0
  41. tests/regscale/integrations/commercial/test_burp.py +48 -0
  42. tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
  43. tests/regscale/integrations/commercial/test_dependabot.py +341 -0
  44. tests/regscale/integrations/commercial/test_gcp.py +1543 -0
  45. tests/regscale/integrations/commercial/test_gitlab.py +549 -0
  46. tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
  47. tests/regscale/integrations/commercial/test_jira.py +1814 -0
  48. tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
  49. tests/regscale/integrations/commercial/test_okta.py +1228 -0
  50. tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
  51. tests/regscale/integrations/commercial/test_sicura.py +350 -0
  52. tests/regscale/integrations/commercial/test_snow.py +423 -0
  53. tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
  54. tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
  55. tests/regscale/integrations/commercial/test_stig.py +33 -0
  56. tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
  57. tests/regscale/integrations/commercial/test_stigv2.py +406 -0
  58. tests/regscale/integrations/commercial/test_wiz.py +1469 -0
  59. tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
  60. tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
  61. tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
  62. tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
  63. tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
  64. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1351 -0
  65. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
  66. tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
  67. tests/regscale/integrations/commercial/wizv2/test_wiz_policy_compliance.py +750 -0
  68. tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
  69. tests/regscale/integrations/commercial/wizv2/test_wizv2.py +264 -0
  70. tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +624 -0
  71. tests/regscale/integrations/public/fedramp/__init__.py +1 -0
  72. tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
  73. tests/regscale/integrations/test_control_matcher.py +1314 -0
  74. tests/regscale/integrations/test_control_matching.py +155 -0
  75. tests/regscale/integrations/test_milestone_manager.py +408 -0
  76. tests/regscale/models/test_issue.py +378 -1
  77. {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.26.0.0.dist-info}/LICENSE +0 -0
  78. {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.26.0.0.dist-info}/WHEEL +0 -0
  79. {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.26.0.0.dist-info}/entry_points.txt +0 -0
  80. {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.26.0.0.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,7 @@ from typing import Dict, List, Optional, Tuple
8
8
  import click
9
9
 
10
10
  from regscale.core.app.application import Application
11
- from regscale.core.app.utils.app_utils import error_and_exit
11
+ from regscale.core.app.utils.app_utils import create_progress_object, error_and_exit
12
12
  from regscale.models.regscale_models import Catalog, SecurityControl, CCI
13
13
 
14
14
  logger = logging.getLogger("regscale")
@@ -109,7 +109,7 @@ class CCIImporter:
109
109
  :rtype: None
110
110
  """
111
111
  if self.verbose:
112
- click.echo("Parsing CCI items from XML...")
112
+ logger.info("Parsing CCI items from XML...")
113
113
 
114
114
  for cci_item in self.xml_data.findall(".//{http://iase.disa.mil/cci}cci_item"):
115
115
  cci_id, definition = self._extract_cci_data(cci_item)
@@ -291,7 +291,7 @@ class CCIImporter:
291
291
  :rtype: Dict[str, int]
292
292
  """
293
293
  if self.verbose:
294
- click.echo("Mapping CCI data to security controls...")
294
+ logger.info("Mapping CCI data to security controls...")
295
295
 
296
296
  catalog = self._get_catalog(catalog_id)
297
297
  security_controls: List[SecurityControl] = SecurityControl.get_all_by_parent(parent_id=catalog.id)
@@ -303,24 +303,28 @@ class CCIImporter:
303
303
  updated_count = 0
304
304
  skipped_count = 0
305
305
 
306
- for main_control, cci_list in self.normalized_cci.items():
307
- if main_control in control_map:
308
- control_id = control_map[main_control]
309
- control_created, control_updated = self._process_cci_for_control(
310
- control_id, cci_list, user_id, tenant_id
311
- )
312
- created_count += control_created
313
- updated_count += control_updated
314
- else:
315
- skipped_count += len(cci_list)
316
- if self.verbose:
317
- click.echo(f"Warning: Control not found for key: {main_control}", err=True)
306
+ with create_progress_object() as progress:
307
+ logger.info(f"Parsing and mapping {len(self.normalized_cci)} normalized CCI entries...")
308
+ main_task = progress.add_task("Parsing and mapping CCIs...", total=len(self.normalized_cci))
309
+ for main_control, cci_list in self.normalized_cci.items():
310
+ if main_control in control_map:
311
+ control_id = control_map[main_control]
312
+ control_created, control_updated = self._process_cci_for_control(
313
+ control_id, cci_list, user_id, tenant_id
314
+ )
315
+ created_count += control_created
316
+ updated_count += control_updated
317
+ else:
318
+ skipped_count += len(cci_list)
319
+ if self.verbose:
320
+ logger.warning(f"Warning: Control not found for key: {main_control}")
321
+ progress.update(main_task, advance=1)
318
322
 
319
323
  return {
320
324
  "created": created_count,
321
325
  "updated": updated_count,
322
326
  "skipped": skipped_count,
323
- "total_processed": len(self.normalized_cci),
327
+ "total_processed": sum(created_count + updated_count + skipped_count),
324
328
  }
325
329
 
326
330
  def get_normalized_cci(self) -> Dict[str, List[Dict]]:
@@ -343,11 +347,10 @@ def _load_xml_file(xml_file: str) -> ET.Element:
343
347
  :raises click.ClickException: If XML parsing fails
344
348
  """
345
349
  try:
346
- click.echo(f"Loading XML file: {xml_file}")
350
+ logger.info(f"Loading XML file: {xml_file}")
347
351
  tree = ET.parse(xml_file)
348
352
  return tree.getroot()
349
353
  except ET.ParseError as e:
350
- click.echo(click.style(f"Failed to parse XML file: {e}", fg="red"), err=True)
351
354
  error_and_exit(f"Failed to parse XML file: {e}")
352
355
 
353
356
 
@@ -358,12 +361,12 @@ def _display_verbose_output(normalized_data: Dict[str, List[Dict]]) -> None:
358
361
  :param Dict[str, List[Dict]] normalized_data: Dictionary of normalized CCI data
359
362
  :rtype: None
360
363
  """
361
- click.echo("\nNormalized CCI Data:")
364
+ logger.info("\nNormalized CCI Data:")
362
365
  for key, value in normalized_data.items():
363
- click.echo(f" {key}: {len(value)} CCI items")
366
+ logger.info(f" {key}: {len(value)} CCI items")
364
367
  for cci in value:
365
368
  definition_preview = cci["definition"][:100] + "..." if len(cci["definition"]) > 100 else cci["definition"]
366
- click.echo(f" - {cci['cci_id']}: {definition_preview}")
369
+ logger.info(f" - {cci['cci_id']}: {definition_preview}")
367
370
 
368
371
 
369
372
  def _display_results(stats: Dict[str, int]) -> None:
@@ -373,15 +376,12 @@ def _display_results(stats: Dict[str, int]) -> None:
373
376
  :param Dict[str, int] stats: Dictionary with operation statistics
374
377
  :rtype: None
375
378
  """
376
- click.echo(
377
- click.style(
378
- f"\nDatabase operations completed:"
379
- f"\n - Created: {stats['created']}"
380
- f"\n - Updated: {stats['updated']}"
381
- f"\n - Skipped: {stats['skipped']}"
382
- f"\n - Total processed: {stats['total_processed']}",
383
- fg="green",
384
- )
379
+ logger.info(
380
+ f"[green]\nDatabase operations completed:"
381
+ f"[green]\n - Created: {stats['created']}"
382
+ f"[green]\n - Updated: {stats['updated']}"
383
+ f"[green]\n - Skipped: {stats['skipped']}"
384
+ f"[green]\n - Total processed: {stats['total_processed']}",
385
385
  )
386
386
 
387
387
 
@@ -398,7 +398,7 @@ def _process_cci_import(importer: CCIImporter, dry_run: bool, verbose: bool, cat
398
398
  importer.parse_cci()
399
399
  normalized_data = importer.get_normalized_cci()
400
400
 
401
- click.echo(click.style(f"Successfully parsed {len(normalized_data)} normalized CCI entries", fg="green"))
401
+ logger.info(f"[green]Successfully parsed {len(normalized_data)} normalized CCI entries[/green]")
402
402
 
403
403
  if verbose:
404
404
  _display_verbose_output(normalized_data)
@@ -407,7 +407,7 @@ def _process_cci_import(importer: CCIImporter, dry_run: bool, verbose: bool, cat
407
407
  stats = importer.map_to_security_controls(catalog_id)
408
408
  _display_results(stats)
409
409
  else:
410
- click.echo(click.style("\nDRY RUN MODE: No database changes were made", fg="yellow"))
410
+ logger.info("\n[yellow]DRY RUN MODE: No database changes were made[/yellow]")
411
411
 
412
412
 
413
413
  @click.command(name="cci_importer")
@@ -431,14 +431,13 @@ def cci_importer(xml_file: str, dry_run: bool, verbose: bool, nist_version: str,
431
431
  try:
432
432
  if not xml_file:
433
433
  import importlib.resources as pkg_resources
434
+ from regscale.models import integration_models
434
435
 
435
- xml_file = pkg_resources.path("regscale.models.integration_models", "CCI_List.xml")
436
+ files = pkg_resources.files(integration_models)
437
+ cci_path = files / "CCI_List.xml"
438
+ xml_file = str(cci_path)
436
439
  root = _load_xml_file(xml_file)
437
440
  importer = CCIImporter(root, version=nist_version, verbose=verbose)
438
441
  _process_cci_import(importer, dry_run, verbose, catalog_id)
439
-
440
- except click.ClickException:
441
- raise
442
442
  except Exception as e:
443
- click.echo(click.style(f"Unexpected error: {e}", fg="red"), err=True)
444
- raise click.ClickException(f"Unexpected error: {e}")
443
+ error_and_exit(f"Unexpected error: {e}")
@@ -20,6 +20,59 @@ def fedramp():
20
20
  """Performs bulk processing of FedRAMP files (Upload trusted data only)."""
21
21
 
22
22
 
23
+ @fedramp.command(context_settings={"show_default": True})
24
+ @click.option(
25
+ "--ssp_id",
26
+ "-s",
27
+ type=click.STRING,
28
+ required=True,
29
+ prompt="Enter the SSP ID to export POAMs from",
30
+ help="The RegScale SSP ID to export POAMs from",
31
+ )
32
+ @click.option(
33
+ "--output_file",
34
+ "-o",
35
+ type=click.STRING,
36
+ required=True,
37
+ prompt="Enter the output file path (xlsx)",
38
+ help="The output file path for the POAM export (xlsx format)",
39
+ )
40
+ @click.option(
41
+ "--template_path",
42
+ "-t",
43
+ type=click.Path(exists=True, dir_okay=False, file_okay=True),
44
+ required=False,
45
+ help="Path to the FedRAMP POAM template Excel file (defaults to ./templates/FedRAMP-POAM-Template.xlsx)",
46
+ )
47
+ @click.option(
48
+ "--point_of_contact",
49
+ "-p",
50
+ type=click.STRING,
51
+ required=False,
52
+ default="",
53
+ help="Point of Contact name for POAMs (defaults to empty string)",
54
+ )
55
+ def export_poam_v5(ssp_id: str, output_file: str, template_path: Optional[click.Path], point_of_contact: str):
56
+ """
57
+ Export FedRAMP Rev 5 POAM Excel file with advanced formatting.
58
+
59
+ This export includes:
60
+ - Dynamic POAM ID generation based on source file paths
61
+ - KEV date determination from CISA KEV catalog
62
+ - Deviation status mapping (Approved/Pending/Rejected)
63
+ - Custom milestone and comment generation
64
+ - Excel formatting for Rev 5 template
65
+ - Configurable Point of Contact
66
+ """
67
+ from pathlib import Path
68
+ from regscale.integrations.public.fedramp.poam_export_v5 import export_poam_v5 as export_func
69
+
70
+ logger.info(f"Exporting FedRAMP Rev 5 POAM for SSP {ssp_id}")
71
+
72
+ template = Path(template_path) if template_path else None
73
+ export_func(ssp_id=ssp_id, output_file=output_file, template_path=template, point_of_contact=point_of_contact)
74
+
75
+
23
76
  # FedRAMP Docx Support
24
77
  @fedramp.command(context_settings={"show_default": True})
25
78
  @click.option(
@@ -108,7 +161,9 @@ def load_fedramp_docx(
108
161
  "by using the -p flag."
109
162
  )
110
163
 
111
- process_fedramp_docx_v5(file_path, base_fedramp_profile_id, save_data, add_missing, appendix_a_file_path) # type: ignore
164
+ process_fedramp_docx_v5(
165
+ file_path, base_fedramp_profile_id, save_data, add_missing, appendix_a_file_path
166
+ ) # type: ignore
112
167
 
113
168
 
114
169
  @fedramp.command()
@@ -435,7 +490,10 @@ def import_drf(file_path: click.Path, regscale_id: int, regscale_module: str) ->
435
490
  "--profile_id",
436
491
  "-p",
437
492
  type=click.INT,
438
- help="The ID number from RegScale of the Profile. (This will generate the control implementations for a new Security Plan)",
493
+ help=(
494
+ "The ID number from RegScale of the Profile. (This will generate the control implementations "
495
+ "for a new Security Plan)"
496
+ ),
439
497
  prompt="Enter RegScale Profile ID",
440
498
  required=True,
441
499
  )