regscale-cli 6.23.0.1__py3-none-any.whl → 6.24.0.1__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 (45) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/application.py +2 -0
  3. regscale/integrations/commercial/__init__.py +1 -0
  4. regscale/integrations/commercial/jira.py +95 -22
  5. regscale/integrations/commercial/sarif/sarif_converter.py +1 -1
  6. regscale/integrations/commercial/wizv2/click.py +132 -2
  7. regscale/integrations/commercial/wizv2/compliance_report.py +1574 -0
  8. regscale/integrations/commercial/wizv2/constants.py +72 -2
  9. regscale/integrations/commercial/wizv2/data_fetcher.py +61 -0
  10. regscale/integrations/commercial/wizv2/file_cleanup.py +104 -0
  11. regscale/integrations/commercial/wizv2/issue.py +775 -27
  12. regscale/integrations/commercial/wizv2/policy_compliance.py +599 -181
  13. regscale/integrations/commercial/wizv2/reports.py +243 -0
  14. regscale/integrations/commercial/wizv2/scanner.py +668 -245
  15. regscale/integrations/compliance_integration.py +534 -56
  16. regscale/integrations/due_date_handler.py +210 -0
  17. regscale/integrations/public/cci_importer.py +444 -0
  18. regscale/integrations/scanner_integration.py +718 -153
  19. regscale/models/integration_models/CCI_List.xml +1 -0
  20. regscale/models/integration_models/cisa_kev_data.json +18 -3
  21. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  22. regscale/models/regscale_models/control_implementation.py +13 -3
  23. regscale/models/regscale_models/form_field_value.py +1 -1
  24. regscale/models/regscale_models/milestone.py +1 -0
  25. regscale/models/regscale_models/regscale_model.py +225 -60
  26. regscale/models/regscale_models/security_plan.py +3 -2
  27. regscale/regscale.py +7 -0
  28. {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/METADATA +17 -17
  29. {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/RECORD +45 -28
  30. tests/fixtures/test_fixture.py +13 -8
  31. tests/regscale/integrations/public/__init__.py +0 -0
  32. tests/regscale/integrations/public/test_alienvault.py +220 -0
  33. tests/regscale/integrations/public/test_cci.py +458 -0
  34. tests/regscale/integrations/public/test_cisa.py +1021 -0
  35. tests/regscale/integrations/public/test_emass.py +518 -0
  36. tests/regscale/integrations/public/test_fedramp.py +851 -0
  37. tests/regscale/integrations/public/test_fedramp_cis_crm.py +3661 -0
  38. tests/regscale/integrations/public/test_file_uploads.py +506 -0
  39. tests/regscale/integrations/public/test_oscal.py +453 -0
  40. tests/regscale/models/test_form_field_value_integration.py +304 -0
  41. tests/regscale/models/test_module_integration.py +582 -0
  42. {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/LICENSE +0 -0
  43. {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/WHEEL +0 -0
  44. {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/entry_points.txt +0 -0
  45. {regscale_cli-6.23.0.1.dist-info → regscale_cli-6.24.0.1.dist-info}/top_level.txt +0 -0
regscale/_version.py CHANGED
@@ -33,7 +33,7 @@ def get_version_from_pyproject() -> str:
33
33
  return match.group(1)
34
34
  except Exception:
35
35
  pass
36
- return "6.23.0.1" # fallback version
36
+ return "6.24.0.1" # fallback version
37
37
 
38
38
 
39
39
  __version__ = get_version_from_pyproject()
@@ -214,6 +214,7 @@ class Application(metaclass=Singleton):
214
214
  "low": 365,
215
215
  "medium": 90,
216
216
  "status": "Open",
217
+ "minimumSeverity": "low",
217
218
  },
218
219
  "xray": {
219
220
  "critical": 30,
@@ -264,6 +265,7 @@ class Application(metaclass=Singleton):
264
265
  "token": DEFAULT_POPULATED,
265
266
  "userId": "enter RegScale user id here",
266
267
  "useMilestones": False,
268
+ "preventAutoClose": True,
267
269
  "otx": "enter AlienVault API key here",
268
270
  "wizAccessToken": DEFAULT_POPULATED,
269
271
  "wizAuthUrl": "https://auth.wiz.io/oauth/token",
@@ -491,6 +491,7 @@ show_mapping(veracode, "veracode")
491
491
  "vulnerabilities": "regscale.integrations.commercial.wizv2.click.vulnerabilities",
492
492
  "add_report_evidence": "regscale.integrations.commercial.wizv2.click.add_report_evidence",
493
493
  "sync_compliance": "regscale.integrations.commercial.wizv2.click.sync_compliance",
494
+ "compliance_report": "regscale.integrations.commercial.wizv2.click.compliance_report",
494
495
  },
495
496
  name="wiz",
496
497
  )
@@ -719,10 +719,7 @@ def create_and_update_regscale_issues(args: Tuple, thread: int) -> None:
719
719
  parent_module=parent_module,
720
720
  )
721
721
  # create the issue in RegScale
722
- if regscale_issue := Issue.insert_issue(
723
- app=app,
724
- issue=issue,
725
- ):
722
+ if regscale_issue := issue.create():
726
723
  logger.debug(
727
724
  "Created issue #%i-%s in RegScale.",
728
725
  regscale_issue.id,
@@ -812,7 +809,7 @@ def fetch_jira_objects(
812
809
  jira_client: JIRA, jira_project: str, jira_issue_type: str, jql_str: str = None, sync_tasks_only: bool = False
813
810
  ) -> list[jiraIssue]:
814
811
  """
815
- Fetch all issues from Jira for the provided project
812
+ Fetch all issues from Jira for the provided project using the enhanced search API.
816
813
 
817
814
  :param JIRA jira_client: Jira client to use for the request
818
815
  :param str jira_project: Name of the project in Jira
@@ -822,15 +819,77 @@ def fetch_jira_objects(
822
819
  :return: List of Jira issues
823
820
  :rtype: list[jiraIssue]
824
821
  """
825
- start_pointer = 0
826
- page_size = 100
827
- jira_objects = []
828
822
  if sync_tasks_only:
829
823
  validate_issue_type(jira_client, jira_issue_type)
830
824
  output_str = "task"
831
825
  else:
832
826
  output_str = "issue"
833
827
  logger.info("Fetching %s(s) from Jira...", output_str.lower())
828
+ try:
829
+ max_results = 100 # 100 is the max allowed by Jira
830
+ jira_issues = []
831
+ issue_response = jira_client.enhanced_search_issues(
832
+ jql_str=jql_str or f"project = {jira_project}",
833
+ maxResults=max_results,
834
+ )
835
+ jira_issues.extend(issue_response)
836
+ logger.info(
837
+ "%i Jira %s(s) retrieved.",
838
+ len(jira_issues),
839
+ output_str.lower(),
840
+ )
841
+ # Handle pagination if there are more issues to fetch
842
+ while issue_response.nextPageToken:
843
+ issue_response = jira_client.enhanced_search_issues(
844
+ jql_str=jql_str, maxResults=max_results, nextPageToken=issue_response.nextPageToken
845
+ )
846
+ jira_issues.extend(issue_response)
847
+ logger.info(
848
+ "%i Jira %s(s) retrieved.",
849
+ len(jira_issues),
850
+ output_str.lower(),
851
+ )
852
+ # Save artifacts file and log final result if we have issues
853
+ if jira_issues:
854
+ save_jira_issues(jira_issues, jira_project, jira_issue_type)
855
+ logger.info("%i %s(s) retrieved from Jira.", len(jira_issues), output_str.lower())
856
+ return jira_issues
857
+ except Exception as e:
858
+ logger.warning(
859
+ "An error occurred while fetching Jira issues using the enhanced_search_issues method: %s", str(e)
860
+ )
861
+ logger.info("Falling back to the deprecated fetch method...")
862
+
863
+ try:
864
+ return deprecated_fetch_jira_objects(
865
+ jira_client=jira_client,
866
+ jira_project=jira_project,
867
+ jira_issue_type=jira_issue_type,
868
+ jql_str=jql_str,
869
+ output_str=output_str,
870
+ )
871
+ except JIRAError as e:
872
+ error_and_exit(f"Unable to fetch issues from Jira: {e}")
873
+
874
+
875
+ def deprecated_fetch_jira_objects(
876
+ jira_client: JIRA, jira_project: str, jira_issue_type: str, jql_str: str = None, output_str: str = "issue"
877
+ ) -> list[jiraIssue]:
878
+ """
879
+ Fetch all issues from Jira for the provided project using the old API method, used as a fallback method.
880
+
881
+ :param JIRA jira_client: Jira client to use for the request
882
+ :param str jira_project: Name of the project in Jira
883
+ :param str jira_issue_type: Type of issue to fetch from Jira
884
+ :param str jql_str: JQL string to use for the request, default None
885
+ :param str output_str: String to use for logging, either "issue" or "task"
886
+ :return: List of Jira issues
887
+ :rtype: list[jiraIssue]
888
+ """
889
+ start_pointer = 0
890
+ page_size = 100
891
+ jira_objects = []
892
+ logger.info("Fetching %s(s) from Jira...", output_str.lower())
834
893
  # get all issues for the Jira project
835
894
  while True:
836
895
  start = start_pointer * page_size
@@ -851,24 +910,36 @@ def fetch_jira_objects(
851
910
  output_str.lower(),
852
911
  )
853
912
  if jira_objects:
854
- check_file_path("artifacts")
855
- file_name = f"{jira_project.lower()}_existingJira{jira_issue_type}.json"
856
- file_path = Path(f"./artifacts/{file_name}")
857
- save_data_to(
858
- file=file_path,
859
- data=[issue.raw for issue in jira_objects],
860
- output_log=False,
861
- )
862
- logger.info(
863
- "Saved %i Jira %s(s), see %s",
864
- len(jira_objects),
865
- jira_issue_type.lower(),
866
- str(file_path.absolute()),
867
- )
913
+ save_jira_issues(jira_objects, jira_project, jira_issue_type)
868
914
  logger.info("%i %s(s) retrieved from Jira.", len(jira_objects), output_str.lower())
869
915
  return jira_objects
870
916
 
871
917
 
918
+ def save_jira_issues(jira_issues: list[jiraIssue], jira_project: str, jira_issue_type: str) -> None:
919
+ """
920
+ Save Jira issues to a JSON file in the artifacts directory
921
+
922
+ :param list[jiraIssue] jira_issues: List of Jira issues to save
923
+ :param str jira_project: Name of the project in Jira
924
+ :param str jira_issue_type: Type of issue to fetch from Jira
925
+ :rtype: None
926
+ """
927
+ check_file_path("artifacts")
928
+ file_name = f"{jira_project.lower()}_existingJira{jira_issue_type}.json"
929
+ file_path = Path(f"./artifacts/{file_name}")
930
+ save_data_to(
931
+ file=file_path,
932
+ data=[issue.raw for issue in jira_issues],
933
+ output_log=False,
934
+ )
935
+ logger.info(
936
+ "Saved %i Jira %s(s), see %s",
937
+ len(jira_issues),
938
+ jira_issue_type.lower(),
939
+ str(file_path.absolute()),
940
+ )
941
+
942
+
872
943
  def map_jira_to_regscale_issue(jira_issue: jiraIssue, config: dict, parent_id: int, parent_module: str) -> Issue:
873
944
  """
874
945
  Map Jira issues to RegScale issues
@@ -893,6 +964,8 @@ def map_jira_to_regscale_issue(jira_issue: jiraIssue, config: dict, parent_id: i
893
964
  ),
894
965
  status=("Closed" if jira_issue.fields.status.name.lower() == "done" else config["issues"]["jira"]["status"]),
895
966
  jiraId=jira_issue.key,
967
+ identification="Jira Sync",
968
+ sourceReport="Jira",
896
969
  parentId=parent_id,
897
970
  parentModule=parent_module,
898
971
  dateCreated=get_current_datetime(),
@@ -44,7 +44,7 @@ def sarif():
44
44
  type=click.DateTime(formats=["%Y-%m-%d"]),
45
45
  help="The scan date of the file.",
46
46
  required=False,
47
- default=get_current_datetime(),
47
+ default=get_current_datetime("%Y-%m-%d"),
48
48
  )
49
49
  def import_sarif(file_path: Path, asset_id: int, scan_date: Optional[datetime.datetime] = None) -> None:
50
50
  """Convert a SARIF file(s) to OCSF format using an API converter."""
@@ -154,7 +154,7 @@ def issues(
154
154
  scanner = WizIssue(plan_id=regscale_ssp_id)
155
155
  scanner.sync_findings(
156
156
  plan_id=regscale_ssp_id,
157
- filter_by_override=filter_by_override, # type: ignore
157
+ filter_by_override=filter_by, # Pass the processed dict with project ID
158
158
  client_id=client_id, # type: ignore
159
159
  client_secret=client_secret, # type: ignore
160
160
  wiz_project_id=wiz_project_id,
@@ -328,7 +328,11 @@ def add_report_evidence(
328
328
  )
329
329
 
330
330
 
331
- @wiz.command("sync_compliance")
331
+ @wiz.command(
332
+ "sync_compliance",
333
+ deprecated=True,
334
+ help="[BETA] This command shows an experimental feature. Use with caution. Use compliance report instead for Compliance sync from Wiz.",
335
+ )
332
336
  @click.option( # type: ignore
333
337
  "--wiz_project_id",
334
338
  "-p",
@@ -476,3 +480,129 @@ def sync_compliance(
476
480
  create_issues=create_issues,
477
481
  update_control_status=update_control_status,
478
482
  )
483
+
484
+
485
+ @wiz.command(name="compliance_report")
486
+ @click.option(
487
+ "--wiz_project_id",
488
+ "-p",
489
+ prompt="Enter the Wiz project ID",
490
+ help="Enter the Wiz Project ID for compliance report processing.",
491
+ required=True,
492
+ )
493
+ @regscale_id(help="RegScale will create and update control assessments as children of this record.")
494
+ @regscale_module(required=True, default="securityplans", prompt=False)
495
+ @click.option(
496
+ "--client_id",
497
+ "-i",
498
+ help="Wiz Client ID, or can be set as environment variable wizClientId",
499
+ default="",
500
+ hide_input=False,
501
+ required=False,
502
+ )
503
+ @click.option(
504
+ "--client_secret",
505
+ "-s",
506
+ help="Wiz Client Secret, or can be set as environment variable wizClientSecret",
507
+ default="",
508
+ hide_input=True,
509
+ required=False,
510
+ )
511
+ @click.option(
512
+ "--report_file_path",
513
+ "-f",
514
+ help="Path to existing CSV compliance report file (optional - will create new report if not provided)",
515
+ default=None,
516
+ required=False,
517
+ )
518
+ @click.option(
519
+ "--create-issues/--no-create-issues",
520
+ "-ci/-ni",
521
+ default=True,
522
+ help="Create issues for failed compliance assessments (default: enabled)",
523
+ )
524
+ @click.option(
525
+ "--update-control-status/--no-update-control-status",
526
+ "-ucs/-nucs",
527
+ default=True,
528
+ help="Update control implementation status based on assessment results (default: enabled)",
529
+ )
530
+ @click.option(
531
+ "--create-poams/--no-create-poams",
532
+ "-cp/-ncp",
533
+ default=False,
534
+ help="Mark created issues as POAMs (default: disabled)",
535
+ )
536
+ @click.option(
537
+ "--reuse-existing-reports/--no-reuse-existing-reports",
538
+ "-rer/-nrer",
539
+ default=True,
540
+ help="Reuse existing Wiz compliance reports instead of creating new ones (default: enabled)",
541
+ )
542
+ @click.option(
543
+ "--force-fresh-report/--no-force-fresh-report",
544
+ "-ffr/-nffr",
545
+ default=False,
546
+ help="Force creation of a fresh compliance report, ignoring existing reports (default: disabled)",
547
+ )
548
+ def compliance_report(
549
+ wiz_project_id,
550
+ regscale_id,
551
+ regscale_module,
552
+ client_id,
553
+ client_secret,
554
+ report_file_path,
555
+ create_issues,
556
+ update_control_status,
557
+ create_poams,
558
+ reuse_existing_reports,
559
+ force_fresh_report,
560
+ ):
561
+ """
562
+ Process Wiz compliance reports and create assessments in RegScale.
563
+
564
+ This command can either:
565
+ 1. Create a new compliance report from Wiz and process it
566
+ 2. Process an existing compliance report CSV file
567
+
568
+ The command will:
569
+ - Parse compliance assessment data from CSV format
570
+ - Create control assessments based on compliance results
571
+ - Create issues for failed compliance assessments (if --create-issues enabled)
572
+ - Update control implementation status (if --update-control-status enabled)
573
+ - Support POAM creation for compliance issues
574
+
575
+ REPORT MANAGEMENT:
576
+ By default, the command will look for existing compliance reports in Wiz for the
577
+ specified project and rerun them instead of creating new ones. This prevents the
578
+ accumulation of duplicate reports in Wiz. Use --no-reuse-existing-reports to
579
+ always create new reports, or --force-fresh-report to force a new report even
580
+ when reuse is enabled.
581
+ """
582
+ from regscale.integrations.commercial.wizv2.compliance_report import WizComplianceReportProcessor
583
+
584
+ # Use environment variables if not provided
585
+ if not client_secret:
586
+ client_secret = WizVariables.wizClientSecret
587
+ if not client_id:
588
+ client_id = WizVariables.wizClientId
589
+
590
+ # Create and run the compliance report processor
591
+ # Enable bypass_control_filtering by default for performance with large control sets
592
+ processor = WizComplianceReportProcessor(
593
+ plan_id=regscale_id,
594
+ wiz_project_id=wiz_project_id,
595
+ client_id=client_id,
596
+ client_secret=client_secret,
597
+ regscale_module=regscale_module,
598
+ create_poams=create_poams,
599
+ create_issues=create_issues,
600
+ update_control_status=update_control_status,
601
+ report_file_path=report_file_path,
602
+ bypass_control_filtering=True, # Bypass filtering for performance with large control sets
603
+ reuse_existing_reports=reuse_existing_reports,
604
+ force_fresh_report=force_fresh_report,
605
+ )
606
+
607
+ # Process the compliance report using new ComplianceIntegration pattern
608
+ processor.process_compliance_sync()