regscale-cli 6.24.0.0__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.
- regscale/_version.py +1 -1
- regscale/integrations/commercial/jira.py +95 -22
- regscale/integrations/commercial/wizv2/click.py +23 -0
- regscale/integrations/commercial/wizv2/compliance_report.py +115 -26
- regscale/integrations/compliance_integration.py +230 -5
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/regscale_models/control_implementation.py +13 -3
- {regscale_cli-6.24.0.0.dist-info → regscale_cli-6.24.0.1.dist-info}/METADATA +9 -9
- {regscale_cli-6.24.0.0.dist-info → regscale_cli-6.24.0.1.dist-info}/RECORD +13 -13
- {regscale_cli-6.24.0.0.dist-info → regscale_cli-6.24.0.1.dist-info}/LICENSE +0 -0
- {regscale_cli-6.24.0.0.dist-info → regscale_cli-6.24.0.1.dist-info}/WHEEL +0 -0
- {regscale_cli-6.24.0.0.dist-info → regscale_cli-6.24.0.1.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.24.0.0.dist-info → regscale_cli-6.24.0.1.dist-info}/top_level.txt +0 -0
regscale/_version.py
CHANGED
|
@@ -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 :=
|
|
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
|
-
|
|
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(),
|
|
@@ -533,6 +533,18 @@ def sync_compliance(
|
|
|
533
533
|
default=False,
|
|
534
534
|
help="Mark created issues as POAMs (default: disabled)",
|
|
535
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
|
+
)
|
|
536
548
|
def compliance_report(
|
|
537
549
|
wiz_project_id,
|
|
538
550
|
regscale_id,
|
|
@@ -543,6 +555,8 @@ def compliance_report(
|
|
|
543
555
|
create_issues,
|
|
544
556
|
update_control_status,
|
|
545
557
|
create_poams,
|
|
558
|
+
reuse_existing_reports,
|
|
559
|
+
force_fresh_report,
|
|
546
560
|
):
|
|
547
561
|
"""
|
|
548
562
|
Process Wiz compliance reports and create assessments in RegScale.
|
|
@@ -557,6 +571,13 @@ def compliance_report(
|
|
|
557
571
|
- Create issues for failed compliance assessments (if --create-issues enabled)
|
|
558
572
|
- Update control implementation status (if --update-control-status enabled)
|
|
559
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.
|
|
560
581
|
"""
|
|
561
582
|
from regscale.integrations.commercial.wizv2.compliance_report import WizComplianceReportProcessor
|
|
562
583
|
|
|
@@ -579,6 +600,8 @@ def compliance_report(
|
|
|
579
600
|
update_control_status=update_control_status,
|
|
580
601
|
report_file_path=report_file_path,
|
|
581
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,
|
|
582
605
|
)
|
|
583
606
|
|
|
584
607
|
# Process the compliance report using new ComplianceIntegration pattern
|
|
@@ -103,27 +103,42 @@ class WizComplianceReportItem(ComplianceItem):
|
|
|
103
103
|
return control_ids[0] if control_ids else ""
|
|
104
104
|
|
|
105
105
|
def get_all_control_ids(self) -> list:
|
|
106
|
-
"""Extract all control IDs from compliance check name."""
|
|
106
|
+
"""Extract all control IDs from compliance check name and normalize leading zeros."""
|
|
107
107
|
if not self.compliance_check_name:
|
|
108
108
|
return []
|
|
109
109
|
|
|
110
|
-
# Parse control IDs from compliance check name
|
|
111
|
-
# Format: "AC-2(4) Account Management | Automated Audit Actions, AC-6(9) Least Privilege | Log Use of Privileged Functions"
|
|
112
|
-
# Use a regex that can find control IDs anywhere in the text
|
|
113
110
|
control_id_pattern = r"([A-Za-z]{2}-\d+)(?:\s*\(\s*(\d+)\s*\))?"
|
|
114
|
-
|
|
115
111
|
control_ids = []
|
|
112
|
+
|
|
116
113
|
for part in self.compliance_check_name.split(", "):
|
|
117
114
|
matches = re.findall(control_id_pattern, part.strip())
|
|
118
115
|
for match in matches:
|
|
119
116
|
base_control, enhancement = match
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
control_ids.append(base_control)
|
|
117
|
+
normalized_control = self._normalize_base_control(base_control)
|
|
118
|
+
formatted_control = self._format_control_id(normalized_control, enhancement)
|
|
119
|
+
control_ids.append(formatted_control)
|
|
124
120
|
|
|
125
121
|
return control_ids
|
|
126
122
|
|
|
123
|
+
def _normalize_base_control(self, base_control: str) -> str:
|
|
124
|
+
"""Normalize leading zeros in base control number (e.g., AC-01 -> AC-1)."""
|
|
125
|
+
if "-" in base_control:
|
|
126
|
+
prefix, number = base_control.split("-", 1)
|
|
127
|
+
try:
|
|
128
|
+
normalized_number = str(int(number))
|
|
129
|
+
return f"{prefix.upper()}-{normalized_number}"
|
|
130
|
+
except ValueError:
|
|
131
|
+
return base_control.upper()
|
|
132
|
+
else:
|
|
133
|
+
return base_control.upper()
|
|
134
|
+
|
|
135
|
+
def _format_control_id(self, base_control: str, enhancement: str) -> str:
|
|
136
|
+
"""Format control ID with optional enhancement."""
|
|
137
|
+
if enhancement:
|
|
138
|
+
return f"{base_control}({enhancement})"
|
|
139
|
+
else:
|
|
140
|
+
return base_control
|
|
141
|
+
|
|
127
142
|
@property
|
|
128
143
|
def affected_controls(self) -> str:
|
|
129
144
|
"""Get affected controls as comma-separated string for issues."""
|
|
@@ -217,6 +232,7 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
217
232
|
bypass_control_filtering: bool = False,
|
|
218
233
|
max_report_age_days: int = 7,
|
|
219
234
|
force_fresh_report: bool = False,
|
|
235
|
+
reuse_existing_reports: bool = True,
|
|
220
236
|
**kwargs,
|
|
221
237
|
):
|
|
222
238
|
"""
|
|
@@ -232,6 +248,7 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
232
248
|
:param bool bypass_control_filtering: Skip control filtering for performance with large control sets
|
|
233
249
|
:param int max_report_age_days: Maximum age in days for reusing existing reports (default: 7 days)
|
|
234
250
|
:param bool force_fresh_report: Force creation of fresh report, ignoring existing reports
|
|
251
|
+
:param bool reuse_existing_reports: Whether to reuse existing Wiz reports instead of creating new ones (default: True)
|
|
235
252
|
"""
|
|
236
253
|
# Call parent constructor with ComplianceIntegration parameters
|
|
237
254
|
super().__init__(
|
|
@@ -250,6 +267,7 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
250
267
|
self.bypass_control_filtering = bypass_control_filtering
|
|
251
268
|
self.max_report_age_days = max_report_age_days
|
|
252
269
|
self.force_fresh_report = force_fresh_report
|
|
270
|
+
self.reuse_existing_reports = reuse_existing_reports
|
|
253
271
|
self.title = "Wiz Compliance" # Required by ScannerIntegration
|
|
254
272
|
|
|
255
273
|
# Initialize Wiz authentication
|
|
@@ -698,7 +716,7 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
698
716
|
# Handle force fresh report request
|
|
699
717
|
if self.force_fresh_report:
|
|
700
718
|
logger.info("Force fresh report requested, creating new compliance report...")
|
|
701
|
-
return self._create_and_download_report()
|
|
719
|
+
return self._create_and_download_report(force_new=True)
|
|
702
720
|
|
|
703
721
|
# Use instance variable max_report_age_days or legacy max_age_hours
|
|
704
722
|
if max_age_hours is not None:
|
|
@@ -756,23 +774,82 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
756
774
|
logger.info(f"Found recent report (age: {age_hours:.1f}h): {most_recent[0]}")
|
|
757
775
|
return most_recent[0]
|
|
758
776
|
|
|
759
|
-
def
|
|
777
|
+
def _find_existing_compliance_report(self) -> Optional[str]:
|
|
760
778
|
"""
|
|
761
|
-
|
|
779
|
+
Find existing compliance report for the current project.
|
|
762
780
|
|
|
763
|
-
:return:
|
|
781
|
+
:return: Report ID if found, None otherwise
|
|
764
782
|
:rtype: Optional[str]
|
|
765
783
|
"""
|
|
766
|
-
|
|
784
|
+
try:
|
|
785
|
+
# Filter for compliance reports for this specific project
|
|
786
|
+
filter_by = {"project": [self.wiz_project_id], "type": ["COMPLIANCE_ASSESSMENTS"]}
|
|
787
|
+
|
|
788
|
+
logger.debug(f"Searching for existing compliance reports with filter: {filter_by}")
|
|
789
|
+
reports = self.report_manager.list_reports(filter_by=filter_by)
|
|
790
|
+
|
|
791
|
+
if not reports:
|
|
792
|
+
logger.info("No existing compliance reports found for this project")
|
|
793
|
+
return None
|
|
794
|
+
|
|
795
|
+
# Look for reports named "Compliance Report" (the default name)
|
|
796
|
+
compliance_reports = [report for report in reports if report.get("name", "").strip() == "Compliance Report"]
|
|
797
|
+
|
|
798
|
+
if not compliance_reports:
|
|
799
|
+
logger.info("No compliance reports with standard name found")
|
|
800
|
+
return None
|
|
801
|
+
|
|
802
|
+
# Return the first matching report (most recent will be used)
|
|
803
|
+
selected_report = compliance_reports[0]
|
|
804
|
+
report_id = selected_report.get("id")
|
|
805
|
+
report_name = selected_report.get("name", "Unknown")
|
|
806
|
+
|
|
807
|
+
logger.info(f"Found existing compliance report: '{report_name}' (ID: {report_id})")
|
|
808
|
+
return report_id
|
|
767
809
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
if not report_id:
|
|
771
|
-
logger.error("Failed to create compliance report")
|
|
810
|
+
except Exception as e:
|
|
811
|
+
logger.error(f"Error searching for existing compliance reports: {e}")
|
|
772
812
|
return None
|
|
773
813
|
|
|
774
|
-
|
|
775
|
-
|
|
814
|
+
def _create_and_download_report(self, force_new: bool = False) -> Optional[str]:
|
|
815
|
+
"""
|
|
816
|
+
Find existing compliance report and rerun it, or create a new one if none exists.
|
|
817
|
+
|
|
818
|
+
:param bool force_new: Force creation of new report, skip reuse logic
|
|
819
|
+
:return: Path to downloaded report file
|
|
820
|
+
:rtype: Optional[str]
|
|
821
|
+
"""
|
|
822
|
+
if force_new or not self.reuse_existing_reports:
|
|
823
|
+
logger.info("Creating new compliance report (reuse disabled or forced)")
|
|
824
|
+
# Create new report
|
|
825
|
+
report_id = self.report_manager.create_compliance_report(self.wiz_project_id)
|
|
826
|
+
if not report_id:
|
|
827
|
+
logger.error("Failed to create compliance report")
|
|
828
|
+
return None
|
|
829
|
+
|
|
830
|
+
# Wait for completion and get download URL
|
|
831
|
+
download_url = self.report_manager.wait_for_report_completion(report_id)
|
|
832
|
+
else:
|
|
833
|
+
logger.info(f"Looking for existing compliance report for project: {self.wiz_project_id}")
|
|
834
|
+
|
|
835
|
+
# Try to find existing compliance report for this project
|
|
836
|
+
if existing_report_id := self._find_existing_compliance_report():
|
|
837
|
+
logger.info(
|
|
838
|
+
f"Found existing compliance report {existing_report_id}, rerunning instead of creating new one"
|
|
839
|
+
)
|
|
840
|
+
# Rerun existing report
|
|
841
|
+
download_url = self.report_manager.rerun_report(existing_report_id)
|
|
842
|
+
else:
|
|
843
|
+
logger.info("No existing compliance report found, creating new one")
|
|
844
|
+
# Create new report
|
|
845
|
+
report_id = self.report_manager.create_compliance_report(self.wiz_project_id)
|
|
846
|
+
if not report_id:
|
|
847
|
+
logger.error("Failed to create compliance report")
|
|
848
|
+
return None
|
|
849
|
+
|
|
850
|
+
# Wait for completion and get download URL
|
|
851
|
+
download_url = self.report_manager.wait_for_report_completion(report_id)
|
|
852
|
+
|
|
776
853
|
if not download_url:
|
|
777
854
|
logger.error("Failed to get download URL for report")
|
|
778
855
|
return None
|
|
@@ -825,6 +902,10 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
825
902
|
# Prepare batch updates for passing controls
|
|
826
903
|
implementations_to_update = []
|
|
827
904
|
|
|
905
|
+
# Debug: Show what keys are actually in the control_impl_map
|
|
906
|
+
if control_impl_map:
|
|
907
|
+
logger.debug(f"Control implementation map keys: {list(control_impl_map.keys())[:20]}")
|
|
908
|
+
|
|
828
909
|
for control_id in passing_control_ids:
|
|
829
910
|
control_id_lower = control_id.lower()
|
|
830
911
|
logger.debug(f"Looking for control '{control_id_lower}' in implementation map")
|
|
@@ -836,8 +917,10 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
836
917
|
# Get the ControlImplementation object
|
|
837
918
|
impl = ControlImplementation.get_object(object_id=impl_id)
|
|
838
919
|
if impl:
|
|
839
|
-
# Update status
|
|
840
|
-
|
|
920
|
+
# Update status using compliance settings
|
|
921
|
+
new_status = self._get_implementation_status_from_result("Pass")
|
|
922
|
+
logger.debug(f"Setting control {control_id} status from 'Pass' result to: {new_status}")
|
|
923
|
+
impl.status = new_status
|
|
841
924
|
impl.dateLastAssessed = get_current_datetime()
|
|
842
925
|
impl.lastAssessmentResult = "Pass"
|
|
843
926
|
impl.bStatusImplemented = True
|
|
@@ -849,7 +932,7 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
849
932
|
impl.dateLastUpdated = get_current_datetime()
|
|
850
933
|
|
|
851
934
|
implementations_to_update.append(impl.dict())
|
|
852
|
-
logger.info(f"Marking control {control_id} as
|
|
935
|
+
logger.info(f"Marking control {control_id} as {new_status}")
|
|
853
936
|
|
|
854
937
|
# Batch update all implementations
|
|
855
938
|
if implementations_to_update:
|
|
@@ -908,6 +991,10 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
908
991
|
implementations_to_update = []
|
|
909
992
|
controls_not_found = []
|
|
910
993
|
|
|
994
|
+
# Debug: Show what keys are actually in the control_impl_map for comparison
|
|
995
|
+
if control_impl_map:
|
|
996
|
+
logger.debug(f"Control implementation map keys (first 20): {list(control_impl_map.keys())[:20]}")
|
|
997
|
+
|
|
911
998
|
for control_id in control_ids:
|
|
912
999
|
control_id_normalized = control_id.lower()
|
|
913
1000
|
logger.debug(f"Looking for control '{control_id_normalized}' in implementation map")
|
|
@@ -946,8 +1033,10 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
946
1033
|
logger.warning(f"Could not retrieve implementation object for ID {impl_id}")
|
|
947
1034
|
return None
|
|
948
1035
|
|
|
949
|
-
# Update status
|
|
950
|
-
|
|
1036
|
+
# Update status using compliance settings
|
|
1037
|
+
new_status = self._get_implementation_status_from_result("Fail")
|
|
1038
|
+
logger.debug(f"Setting control {control_id} status from 'Fail' result to: {new_status}")
|
|
1039
|
+
impl.status = new_status
|
|
951
1040
|
impl.dateLastAssessed = get_current_datetime()
|
|
952
1041
|
impl.lastAssessmentResult = "Fail"
|
|
953
1042
|
impl.bStatusImplemented = False
|
|
@@ -958,7 +1047,7 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
958
1047
|
impl.lastUpdatedById = user_id
|
|
959
1048
|
impl.dateLastUpdated = get_current_datetime()
|
|
960
1049
|
|
|
961
|
-
logger.info(f"Marking control {control_id} as
|
|
1050
|
+
logger.info(f"Marking control {control_id} as {new_status}")
|
|
962
1051
|
return impl.dict()
|
|
963
1052
|
|
|
964
1053
|
def _log_update_summary(self, implementations_to_update: list, controls_not_found: list) -> None:
|