regscale-cli 6.25.0.1__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.
- regscale/_version.py +1 -1
- regscale/airflow/hierarchy.py +2 -2
- regscale/core/app/application.py +18 -3
- regscale/core/app/internal/login.py +0 -1
- regscale/core/app/utils/catalog_utils/common.py +1 -1
- regscale/integrations/commercial/sicura/api.py +14 -13
- regscale/integrations/commercial/sicura/commands.py +8 -2
- regscale/integrations/commercial/sicura/scanner.py +49 -39
- regscale/integrations/commercial/stigv2/ckl_parser.py +5 -5
- regscale/integrations/commercial/synqly/assets.py +17 -0
- regscale/integrations/commercial/wizv2/click.py +26 -26
- regscale/integrations/commercial/wizv2/compliance_report.py +152 -157
- regscale/integrations/commercial/wizv2/constants.py +20 -71
- regscale/integrations/commercial/wizv2/scanner.py +3 -3
- regscale/integrations/compliance_integration.py +67 -2
- regscale/integrations/control_matcher.py +358 -0
- regscale/integrations/due_date_handler.py +118 -6
- regscale/integrations/milestone_manager.py +291 -0
- regscale/integrations/public/__init__.py +1 -0
- regscale/integrations/public/cci_importer.py +37 -38
- regscale/integrations/public/fedramp/click.py +60 -2
- regscale/integrations/public/fedramp/poam_export_v5.py +888 -0
- regscale/integrations/scanner_integration.py +199 -130
- regscale/models/integration_models/cisa_kev_data.json +199 -4
- regscale/models/integration_models/nexpose.py +36 -10
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/locking.py +12 -8
- regscale/models/platform.py +1 -2
- regscale/models/regscale_models/control_implementation.py +46 -21
- regscale/models/regscale_models/issue.py +256 -94
- regscale/models/regscale_models/milestone.py +1 -1
- regscale/models/regscale_models/regscale_model.py +6 -1
- regscale/templates/__init__.py +0 -0
- regscale/utils/threading/threadhandler.py +20 -15
- {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/RECORD +84 -37
- tests/regscale/integrations/commercial/__init__.py +0 -0
- tests/regscale/integrations/commercial/conftest.py +28 -0
- tests/regscale/integrations/commercial/microsoft_defender/__init__.py +1 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender.py +1517 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_api.py +1748 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_constants.py +327 -0
- tests/regscale/integrations/commercial/microsoft_defender/test_defender_scanner.py +487 -0
- tests/regscale/integrations/commercial/test_aws.py +3731 -0
- tests/regscale/integrations/commercial/test_burp.py +48 -0
- tests/regscale/integrations/commercial/test_crowdstrike.py +49 -0
- tests/regscale/integrations/commercial/test_dependabot.py +341 -0
- tests/regscale/integrations/commercial/test_gcp.py +1543 -0
- tests/regscale/integrations/commercial/test_gitlab.py +549 -0
- tests/regscale/integrations/commercial/test_ip_mac_address_length.py +84 -0
- tests/regscale/integrations/commercial/test_jira.py +1814 -0
- tests/regscale/integrations/commercial/test_npm_audit.py +42 -0
- tests/regscale/integrations/commercial/test_okta.py +1228 -0
- tests/regscale/integrations/commercial/test_sarif_converter.py +251 -0
- tests/regscale/integrations/commercial/test_sicura.py +350 -0
- tests/regscale/integrations/commercial/test_snow.py +423 -0
- tests/regscale/integrations/commercial/test_sonarcloud.py +394 -0
- tests/regscale/integrations/commercial/test_sqlserver.py +186 -0
- tests/regscale/integrations/commercial/test_stig.py +33 -0
- tests/regscale/integrations/commercial/test_stig_mapper.py +153 -0
- tests/regscale/integrations/commercial/test_stigv2.py +406 -0
- tests/regscale/integrations/commercial/test_wiz.py +1469 -0
- tests/regscale/integrations/commercial/test_wiz_inventory.py +256 -0
- tests/regscale/integrations/commercial/wizv2/__init__.py +339 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_issue.py +343 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +165 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1351 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_unit.py +341 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_control_normalization.py +138 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_policy_compliance.py +750 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +264 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +624 -0
- tests/regscale/integrations/public/fedramp/__init__.py +1 -0
- tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
- tests/regscale/integrations/test_control_matcher.py +1314 -0
- tests/regscale/integrations/test_control_matching.py +155 -0
- tests/regscale/integrations/test_milestone_manager.py +408 -0
- tests/regscale/models/test_issue.py +378 -1
- {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.25.0.1.dist-info → regscale_cli-6.26.0.0.dist-info}/top_level.txt +0 -0
|
@@ -17,8 +17,10 @@ from regscale.integrations.commercial.wizv2.reports import WizReportManager
|
|
|
17
17
|
from regscale.integrations.commercial.wizv2.variables import WizVariables
|
|
18
18
|
from regscale.integrations.commercial.wizv2.wiz_auth import wiz_authenticate
|
|
19
19
|
from regscale.integrations.compliance_integration import ComplianceIntegration, ComplianceItem
|
|
20
|
+
from regscale.integrations.control_matcher import ControlMatcher
|
|
20
21
|
from regscale.models import regscale_models
|
|
21
|
-
from regscale.models.regscale_models.control_implementation import ControlImplementation
|
|
22
|
+
from regscale.models.regscale_models.control_implementation import ControlImplementation
|
|
23
|
+
from regscale.models.regscale_models.issue import IssueIdentification
|
|
22
24
|
|
|
23
25
|
logger = logging.getLogger("regscale")
|
|
24
26
|
|
|
@@ -135,7 +137,12 @@ class WizComplianceReportItem(ComplianceItem):
|
|
|
135
137
|
def _format_control_id(self, base_control: str, enhancement: str) -> str:
|
|
136
138
|
"""Format control ID with optional enhancement."""
|
|
137
139
|
if enhancement:
|
|
138
|
-
|
|
140
|
+
# Normalize enhancement number to remove leading zeros
|
|
141
|
+
try:
|
|
142
|
+
normalized_enhancement = str(int(enhancement))
|
|
143
|
+
except ValueError:
|
|
144
|
+
normalized_enhancement = enhancement
|
|
145
|
+
return f"{base_control}({normalized_enhancement})"
|
|
139
146
|
else:
|
|
140
147
|
return base_control
|
|
141
148
|
|
|
@@ -277,6 +284,10 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
277
284
|
|
|
278
285
|
self.report_manager = WizReportManager(WizVariables.wizUrl, access_token)
|
|
279
286
|
|
|
287
|
+
# Initialize control matcher for robust control ID matching (inherited from parent but ensure it's set)
|
|
288
|
+
if not hasattr(self, "_control_matcher"):
|
|
289
|
+
self._control_matcher = ControlMatcher()
|
|
290
|
+
|
|
280
291
|
def parse_csv_report(self, file_path: str) -> List[WizComplianceReportItem]:
|
|
281
292
|
"""
|
|
282
293
|
Parse CSV compliance report.
|
|
@@ -783,7 +794,7 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
783
794
|
"""
|
|
784
795
|
try:
|
|
785
796
|
# Filter for compliance reports for this specific project
|
|
786
|
-
filter_by = {"
|
|
797
|
+
filter_by = {"projectId": [self.wiz_project_id], "type": ["COMPLIANCE_ASSESSMENTS"]}
|
|
787
798
|
|
|
788
799
|
logger.debug(f"Searching for existing compliance reports with filter: {filter_by}")
|
|
789
800
|
reports = self.report_manager.list_reports(filter_by=filter_by)
|
|
@@ -876,76 +887,66 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
876
887
|
"""
|
|
877
888
|
Update passing controls to 'Implemented' status in RegScale.
|
|
878
889
|
|
|
890
|
+
Uses ControlMatcher for robust control ID matching with leading zero normalization.
|
|
891
|
+
|
|
879
892
|
:param list[str] passing_control_ids: List of control IDs that passed
|
|
880
893
|
"""
|
|
881
894
|
if not passing_control_ids:
|
|
882
895
|
return
|
|
883
896
|
|
|
884
|
-
# Initialize Application for control implementation updates
|
|
885
|
-
# app = Application() # Will be used through self.app
|
|
886
|
-
|
|
887
897
|
try:
|
|
888
|
-
# Use the existing method that works for getting control name to implementation ID mapping
|
|
889
|
-
control_impl_map = ControlImplementation.get_control_label_map_by_parent(
|
|
890
|
-
parent_id=self.plan_id, parent_module=self.parent_module
|
|
891
|
-
)
|
|
892
|
-
|
|
893
|
-
logger.debug(
|
|
894
|
-
f"Built control implementation map with {len(control_impl_map)} entries using get_control_label_map_by_parent"
|
|
895
|
-
)
|
|
896
|
-
if control_impl_map:
|
|
897
|
-
sample_keys = list(control_impl_map.keys())[:10]
|
|
898
|
-
logger.debug(f"Sample control names in map: {sample_keys}")
|
|
899
|
-
|
|
900
898
|
logger.debug(f"Looking for passing control IDs: {passing_control_ids}")
|
|
901
899
|
|
|
902
900
|
# Prepare batch updates for passing controls
|
|
903
901
|
implementations_to_update = []
|
|
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]}")
|
|
902
|
+
controls_not_found = []
|
|
908
903
|
|
|
909
904
|
for control_id in passing_control_ids:
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
905
|
+
# Use ControlMatcher to find implementation with robust control ID matching
|
|
906
|
+
impl = self._control_matcher.find_control_implementation(
|
|
907
|
+
control_id=control_id, parent_id=self.plan_id, parent_module=self.parent_module
|
|
908
|
+
)
|
|
909
|
+
|
|
910
|
+
if impl:
|
|
911
|
+
logger.debug(f"Found matching implementation for '{control_id}': {impl.id}")
|
|
912
|
+
|
|
913
|
+
# Update status using compliance settings
|
|
914
|
+
new_status = self._get_implementation_status_from_result("Pass")
|
|
915
|
+
logger.debug(f"Setting control {control_id} status from 'Pass' result to: {new_status}")
|
|
916
|
+
impl.status = new_status
|
|
917
|
+
impl.dateLastAssessed = get_current_datetime()
|
|
918
|
+
impl.lastAssessmentResult = "Pass"
|
|
919
|
+
impl.bStatusImplemented = True
|
|
920
|
+
|
|
921
|
+
# Ensure required fields are set if empty
|
|
922
|
+
if not impl.responsibility:
|
|
923
|
+
impl.responsibility = ControlImplementation.get_default_responsibility(parent_id=impl.parentId)
|
|
924
|
+
logger.debug(f"Setting default responsibility for control {control_id}: {impl.responsibility}")
|
|
925
|
+
|
|
926
|
+
if not impl.implementation:
|
|
927
|
+
impl.implementation = f"Implementation details for {control_id} will be documented."
|
|
928
|
+
logger.debug(f"Setting default implementation statement for control {control_id}")
|
|
929
|
+
|
|
930
|
+
# Set audit fields if available
|
|
931
|
+
user_id = self.app.config.get("userId")
|
|
932
|
+
if user_id:
|
|
933
|
+
impl.lastUpdatedById = user_id
|
|
934
|
+
impl.dateLastUpdated = get_current_datetime()
|
|
935
|
+
|
|
936
|
+
implementations_to_update.append(impl.dict())
|
|
937
|
+
logger.info(f"Marking control {control_id} as {new_status}")
|
|
938
|
+
else:
|
|
939
|
+
logger.debug(f"Control '{control_id}' not found in implementation map")
|
|
940
|
+
controls_not_found.append(control_id)
|
|
941
|
+
|
|
942
|
+
# Log summary
|
|
943
|
+
if controls_not_found:
|
|
944
|
+
logger.info(f"Passing control IDs not found in plan: {', '.join(sorted(controls_not_found))}")
|
|
945
|
+
|
|
946
|
+
logger.info(
|
|
947
|
+
f"Control implementation status update summary: {len(implementations_to_update)} found, "
|
|
948
|
+
f"{len(controls_not_found)} not in plan"
|
|
949
|
+
)
|
|
949
950
|
|
|
950
951
|
# Batch update all implementations
|
|
951
952
|
if implementations_to_update:
|
|
@@ -957,104 +958,47 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
957
958
|
except Exception as e:
|
|
958
959
|
logger.error(f"Error updating control implementation status: {e}")
|
|
959
960
|
|
|
960
|
-
def
|
|
961
|
-
"""
|
|
962
|
-
Update control implementation status to In Remediation for failing controls.
|
|
963
|
-
|
|
964
|
-
:param List[str] control_ids: List of control IDs that are failing
|
|
965
|
-
:return: None
|
|
966
|
-
:rtype: None
|
|
961
|
+
def _prepare_failing_control_update(self, control_id: str) -> Optional[dict]:
|
|
967
962
|
"""
|
|
968
|
-
|
|
969
|
-
return
|
|
970
|
-
|
|
971
|
-
try:
|
|
972
|
-
control_impl_map = self._get_control_implementation_map()
|
|
973
|
-
if not control_impl_map:
|
|
974
|
-
return
|
|
975
|
-
|
|
976
|
-
implementations_to_update, controls_not_found = self._process_failing_control_ids(
|
|
977
|
-
control_ids, control_impl_map
|
|
978
|
-
)
|
|
979
|
-
|
|
980
|
-
self._log_update_summary(implementations_to_update, controls_not_found)
|
|
981
|
-
self._batch_update_implementations(implementations_to_update)
|
|
982
|
-
|
|
983
|
-
except Exception as e:
|
|
984
|
-
logger.error(f"Error updating failing control implementation status: {e}")
|
|
985
|
-
|
|
986
|
-
def _get_control_implementation_map(self) -> dict:
|
|
987
|
-
"""Get control implementation map and validate it exists."""
|
|
988
|
-
|
|
989
|
-
control_impl_map = ControlImplementation.get_control_label_map_by_parent(
|
|
990
|
-
parent_id=self.plan_id, parent_module=self.parent_module
|
|
991
|
-
)
|
|
992
|
-
|
|
993
|
-
if not control_impl_map:
|
|
994
|
-
logger.warning("No control implementation mapping found for security plan")
|
|
995
|
-
return {}
|
|
963
|
+
Prepare a single failing control for update.
|
|
996
964
|
|
|
997
|
-
|
|
998
|
-
return
|
|
999
|
-
|
|
1000
|
-
def _process_failing_control_ids(self, control_ids: List[str], control_impl_map: dict) -> tuple[list, list]:
|
|
1001
|
-
"""Process failing control IDs and return implementations to update and controls not found."""
|
|
1002
|
-
|
|
1003
|
-
logger.debug(f"Looking for failing control IDs: {control_ids}")
|
|
1004
|
-
implementations_to_update = []
|
|
1005
|
-
controls_not_found = []
|
|
1006
|
-
|
|
1007
|
-
# Debug: Show what keys are actually in the control_impl_map for comparison
|
|
1008
|
-
if control_impl_map:
|
|
1009
|
-
logger.debug(f"Control implementation map keys (first 20): {list(control_impl_map.keys())[:20]}")
|
|
1010
|
-
|
|
1011
|
-
for control_id in control_ids:
|
|
1012
|
-
control_id_normalized = control_id.lower()
|
|
1013
|
-
logger.debug(f"Looking for control '{control_id_normalized}' in implementation map")
|
|
1014
|
-
|
|
1015
|
-
if control_id_normalized in control_impl_map:
|
|
1016
|
-
impl = self._update_single_control_implementation(
|
|
1017
|
-
control_id, control_id_normalized, control_impl_map[control_id_normalized]
|
|
1018
|
-
)
|
|
1019
|
-
if impl:
|
|
1020
|
-
implementations_to_update.append(impl)
|
|
1021
|
-
else:
|
|
1022
|
-
controls_not_found.append(control_id)
|
|
1023
|
-
else:
|
|
1024
|
-
logger.debug(f"Control '{control_id_normalized}' not found in implementation map")
|
|
1025
|
-
controls_not_found.append(control_id)
|
|
1026
|
-
|
|
1027
|
-
return implementations_to_update, controls_not_found
|
|
1028
|
-
|
|
1029
|
-
def _update_single_control_implementation(
|
|
1030
|
-
self, control_id: str, control_id_normalized: str, impl_id: int
|
|
1031
|
-
) -> Optional[dict]:
|
|
1032
|
-
"""Update a single control implementation to In Remediation status.
|
|
1033
|
-
:param str control_id: ID of the control to update
|
|
1034
|
-
:param str control_id_normalized: ID of the control to update
|
|
1035
|
-
:param int impl_id: ID of the implementation to update
|
|
1036
|
-
:return: Updated implementation status if implementation exists
|
|
965
|
+
:param str control_id: Control ID to update
|
|
966
|
+
:return: Dictionary representation of updated implementation, or None if not found
|
|
1037
967
|
:rtype: Optional[dict]
|
|
1038
968
|
"""
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
logger.debug(f"Found matching implementation for '{control_id_normalized}': {impl_id}")
|
|
969
|
+
impl = self._control_matcher.find_control_implementation(
|
|
970
|
+
control_id=control_id, parent_id=self.plan_id, parent_module=self.parent_module
|
|
971
|
+
)
|
|
1043
972
|
|
|
1044
|
-
impl = ControlImplementation.get_object(object_id=impl_id)
|
|
1045
973
|
if not impl:
|
|
1046
|
-
logger.
|
|
974
|
+
logger.debug(f"Control '{control_id}' not found in implementation map")
|
|
1047
975
|
return None
|
|
1048
976
|
|
|
1049
|
-
|
|
977
|
+
logger.debug(f"Found matching implementation for '{control_id}': {impl.id}")
|
|
978
|
+
|
|
1050
979
|
new_status = self._get_implementation_status_from_result("Fail")
|
|
1051
980
|
logger.debug(f"Setting control {control_id} status from 'Fail' result to: {new_status}")
|
|
981
|
+
|
|
1052
982
|
impl.status = new_status
|
|
1053
983
|
impl.dateLastAssessed = get_current_datetime()
|
|
1054
984
|
impl.lastAssessmentResult = "Fail"
|
|
1055
985
|
impl.bStatusImplemented = False
|
|
1056
986
|
|
|
1057
|
-
|
|
987
|
+
self._set_default_fields_if_empty(impl, control_id)
|
|
988
|
+
self._set_audit_fields(impl)
|
|
989
|
+
|
|
990
|
+
logger.info(f"Marking control {control_id} as {new_status}")
|
|
991
|
+
return impl.dict()
|
|
992
|
+
|
|
993
|
+
def _set_default_fields_if_empty(self, impl: ControlImplementation, control_id: str) -> None:
|
|
994
|
+
"""
|
|
995
|
+
Set default values for required fields if they are empty.
|
|
996
|
+
|
|
997
|
+
:param ControlImplementation impl: Implementation to update
|
|
998
|
+
:param str control_id: Control ID for logging
|
|
999
|
+
:return: None
|
|
1000
|
+
:rtype: None
|
|
1001
|
+
"""
|
|
1058
1002
|
if not impl.responsibility:
|
|
1059
1003
|
impl.responsibility = ControlImplementation.get_default_responsibility(parent_id=impl.parentId)
|
|
1060
1004
|
logger.debug(f"Setting default responsibility for control {control_id}: {impl.responsibility}")
|
|
@@ -1063,27 +1007,76 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
1063
1007
|
impl.implementation = f"Implementation details for {control_id} will be documented."
|
|
1064
1008
|
logger.debug(f"Setting default implementation statement for control {control_id}")
|
|
1065
1009
|
|
|
1066
|
-
|
|
1010
|
+
def _set_audit_fields(self, impl: ControlImplementation) -> None:
|
|
1011
|
+
"""
|
|
1012
|
+
Set audit fields on implementation if user ID is available.
|
|
1013
|
+
|
|
1014
|
+
:param ControlImplementation impl: Implementation to update
|
|
1015
|
+
:return: None
|
|
1016
|
+
:rtype: None
|
|
1017
|
+
"""
|
|
1067
1018
|
user_id = self.app.config.get("userId")
|
|
1068
1019
|
if user_id:
|
|
1069
1020
|
impl.lastUpdatedById = user_id
|
|
1070
1021
|
impl.dateLastUpdated = get_current_datetime()
|
|
1071
1022
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1023
|
+
def _update_failing_controls_to_in_remediation(self, control_ids: List[str]) -> None:
|
|
1024
|
+
"""
|
|
1025
|
+
Update control implementation status to In Remediation for failing controls.
|
|
1026
|
+
|
|
1027
|
+
Uses ControlMatcher for robust control ID matching with leading zero normalization.
|
|
1028
|
+
|
|
1029
|
+
:param List[str] control_ids: List of control IDs that are failing
|
|
1030
|
+
:return: None
|
|
1031
|
+
:rtype: None
|
|
1032
|
+
"""
|
|
1033
|
+
if not control_ids:
|
|
1034
|
+
return
|
|
1074
1035
|
|
|
1075
|
-
|
|
1076
|
-
|
|
1036
|
+
try:
|
|
1037
|
+
logger.debug(f"Looking for failing control IDs: {control_ids}")
|
|
1038
|
+
|
|
1039
|
+
implementations_to_update = []
|
|
1040
|
+
controls_not_found = []
|
|
1041
|
+
|
|
1042
|
+
for control_id in control_ids:
|
|
1043
|
+
impl_dict = self._prepare_failing_control_update(control_id)
|
|
1044
|
+
if impl_dict:
|
|
1045
|
+
implementations_to_update.append(impl_dict)
|
|
1046
|
+
else:
|
|
1047
|
+
controls_not_found.append(control_id)
|
|
1048
|
+
|
|
1049
|
+
self._log_update_summary(controls_not_found, implementations_to_update)
|
|
1050
|
+
self._batch_update_implementations(implementations_to_update)
|
|
1051
|
+
|
|
1052
|
+
except Exception as e:
|
|
1053
|
+
logger.error(f"Error updating failing control implementation status: {e}")
|
|
1054
|
+
|
|
1055
|
+
def _log_update_summary(self, controls_not_found: List[str], implementations_to_update: List[dict]) -> None:
|
|
1056
|
+
"""
|
|
1057
|
+
Log summary of control update operation.
|
|
1058
|
+
|
|
1059
|
+
:param List[str] controls_not_found: List of controls not found
|
|
1060
|
+
:param List[dict] implementations_to_update: List of implementations to update
|
|
1061
|
+
:return: None
|
|
1062
|
+
:rtype: None
|
|
1063
|
+
"""
|
|
1077
1064
|
if controls_not_found:
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1065
|
+
logger.info(f"Control IDs not found in plan: {', '.join(sorted(controls_not_found))}")
|
|
1066
|
+
|
|
1067
|
+
logger.info(
|
|
1068
|
+
f"Control implementation status update summary: {len(implementations_to_update)} found, "
|
|
1069
|
+
f"{len(controls_not_found)} not in plan"
|
|
1070
|
+
)
|
|
1071
|
+
|
|
1072
|
+
def _batch_update_implementations(self, implementations_to_update: List[dict]) -> None:
|
|
1073
|
+
"""
|
|
1074
|
+
Perform batch update of control implementations.
|
|
1084
1075
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1076
|
+
:param List[dict] implementations_to_update: List of implementations to update
|
|
1077
|
+
:return: None
|
|
1078
|
+
:rtype: None
|
|
1079
|
+
"""
|
|
1087
1080
|
if implementations_to_update:
|
|
1088
1081
|
ControlImplementation.put_batch_implementation(self.app, implementations_to_update)
|
|
1089
1082
|
logger.debug(f"Updated {len(implementations_to_update)} Control Implementations, Successfully!")
|
|
@@ -1551,6 +1544,7 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
1551
1544
|
rule_id=control_id,
|
|
1552
1545
|
baseline=representative_item.framework,
|
|
1553
1546
|
affected_controls=control_id,
|
|
1547
|
+
identification=IssueIdentification.SecurityControlAssessment.value,
|
|
1554
1548
|
)
|
|
1555
1549
|
|
|
1556
1550
|
def _create_finding_from_compliance_item(self, compliance_item: ComplianceItem) -> Optional[Any]:
|
|
@@ -1587,6 +1581,7 @@ class WizComplianceReportProcessor(ComplianceIntegration):
|
|
|
1587
1581
|
rule_id=compliance_item.control_id,
|
|
1588
1582
|
baseline=compliance_item.framework,
|
|
1589
1583
|
affected_controls=compliance_item.affected_controls, # Use our property with all control IDs
|
|
1584
|
+
identification=IssueIdentification.SecurityControlAssessment.value,
|
|
1590
1585
|
)
|
|
1591
1586
|
|
|
1592
1587
|
return finding
|
|
@@ -1171,85 +1171,24 @@ fragment SecuritySubCategoryDetails on SecuritySubCategory {
|
|
|
1171
1171
|
}
|
|
1172
1172
|
"""
|
|
1173
1173
|
DATA_FINDING_QUERY = """
|
|
1174
|
-
query
|
|
1175
|
-
|
|
1176
|
-
groupBy: $groupBy
|
|
1174
|
+
query DataFindingsTable($after: String, $first: Int, $filterBy: DataFindingFiltersV2, $orderBy: DataFindingOrder, $fetchTotalCount: Boolean = true) {
|
|
1175
|
+
dataFindingsV2(
|
|
1177
1176
|
filterBy: $filterBy
|
|
1178
1177
|
first: $first
|
|
1179
1178
|
after: $after
|
|
1180
1179
|
orderBy: $orderBy
|
|
1181
1180
|
) {
|
|
1182
1181
|
nodes {
|
|
1183
|
-
|
|
1184
|
-
location {
|
|
1185
|
-
countryCode
|
|
1186
|
-
state
|
|
1187
|
-
}
|
|
1188
|
-
regionCount
|
|
1189
|
-
graphEntityCount
|
|
1190
|
-
graphEntity {
|
|
1191
|
-
id
|
|
1192
|
-
name
|
|
1193
|
-
type
|
|
1194
|
-
properties
|
|
1195
|
-
projects {
|
|
1196
|
-
id
|
|
1197
|
-
name
|
|
1198
|
-
slug
|
|
1199
|
-
isFolder
|
|
1200
|
-
}
|
|
1201
|
-
issues(filterBy: {status: [OPEN, IN_PROGRESS]}) {
|
|
1202
|
-
criticalSeverityCount
|
|
1203
|
-
highSeverityCount
|
|
1204
|
-
mediumSeverityCount
|
|
1205
|
-
lowSeverityCount
|
|
1206
|
-
informationalSeverityCount
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
cloudAccount {
|
|
1210
|
-
id
|
|
1211
|
-
name
|
|
1212
|
-
externalId
|
|
1213
|
-
cloudProvider
|
|
1214
|
-
}
|
|
1215
|
-
dataClassifiers {
|
|
1216
|
-
id
|
|
1217
|
-
name
|
|
1218
|
-
category
|
|
1219
|
-
matcherType
|
|
1220
|
-
severity
|
|
1221
|
-
}
|
|
1222
|
-
securitySubCategories {
|
|
1223
|
-
id
|
|
1224
|
-
title
|
|
1225
|
-
externalId
|
|
1226
|
-
description
|
|
1227
|
-
category {
|
|
1228
|
-
id
|
|
1229
|
-
name
|
|
1230
|
-
description
|
|
1231
|
-
framework {
|
|
1232
|
-
id
|
|
1233
|
-
name
|
|
1234
|
-
description
|
|
1235
|
-
enabled
|
|
1236
|
-
}
|
|
1237
|
-
}
|
|
1238
|
-
}
|
|
1239
|
-
findingsCount
|
|
1240
|
-
dataFindings(first: 5) {
|
|
1241
|
-
nodes {
|
|
1242
|
-
...DataFindingDetails
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1182
|
+
...DataFindingDetails
|
|
1245
1183
|
}
|
|
1246
1184
|
pageInfo {
|
|
1247
1185
|
hasNextPage
|
|
1248
1186
|
endCursor
|
|
1249
1187
|
}
|
|
1250
|
-
totalCount
|
|
1188
|
+
totalCount @include(if: $fetchTotalCount)
|
|
1251
1189
|
}
|
|
1252
1190
|
}
|
|
1191
|
+
|
|
1253
1192
|
fragment DataFindingDetails on DataFinding {
|
|
1254
1193
|
id
|
|
1255
1194
|
name
|
|
@@ -1257,10 +1196,10 @@ fragment DataFindingDetails on DataFinding {
|
|
|
1257
1196
|
id
|
|
1258
1197
|
name
|
|
1259
1198
|
category
|
|
1199
|
+
isTenantSpecific
|
|
1260
1200
|
securitySubCategories {
|
|
1261
1201
|
id
|
|
1262
1202
|
title
|
|
1263
|
-
externalId
|
|
1264
1203
|
description
|
|
1265
1204
|
category {
|
|
1266
1205
|
id
|
|
@@ -1286,8 +1225,12 @@ fragment DataFindingDetails on DataFinding {
|
|
|
1286
1225
|
state
|
|
1287
1226
|
}
|
|
1288
1227
|
severity
|
|
1228
|
+
status
|
|
1289
1229
|
totalMatchCount
|
|
1290
1230
|
uniqueMatchCount
|
|
1231
|
+
maxUniqueMatchesReached
|
|
1232
|
+
uniqueLocationsCount
|
|
1233
|
+
isEntityPublic
|
|
1291
1234
|
graphEntity {
|
|
1292
1235
|
id
|
|
1293
1236
|
name
|
|
@@ -1301,6 +1244,12 @@ fragment DataFindingDetails on DataFinding {
|
|
|
1301
1244
|
}
|
|
1302
1245
|
}
|
|
1303
1246
|
externalSource
|
|
1247
|
+
details {
|
|
1248
|
+
applicationServices {
|
|
1249
|
+
id
|
|
1250
|
+
displayName
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1304
1253
|
}
|
|
1305
1254
|
"""
|
|
1306
1255
|
|
|
@@ -1387,14 +1336,14 @@ def get_wiz_vulnerability_queries(project_id: str, filter_by: Optional[dict] = N
|
|
|
1387
1336
|
{
|
|
1388
1337
|
"type": WizVulnerabilityType.DATA_FINDING,
|
|
1389
1338
|
"query": DATA_FINDING_QUERY,
|
|
1390
|
-
"topic_key": "
|
|
1339
|
+
"topic_key": "dataFindingsV2",
|
|
1391
1340
|
"file_path": DATA_FINDINGS_FILE_PATH,
|
|
1392
|
-
"asset_lookup": "
|
|
1341
|
+
"asset_lookup": "graphEntity",
|
|
1393
1342
|
"variables": {
|
|
1394
1343
|
"first": 200,
|
|
1344
|
+
"fetchTotalCount": True,
|
|
1395
1345
|
"filterBy": {"projectId": [project_id]},
|
|
1396
|
-
"orderBy": {"field": "
|
|
1397
|
-
"groupBy": "GRAPH_ENTITY",
|
|
1346
|
+
"orderBy": {"field": "TOTAL_MATCHES", "direction": "DESC"},
|
|
1398
1347
|
},
|
|
1399
1348
|
},
|
|
1400
1349
|
{
|
|
@@ -1940,10 +1940,9 @@ class WizVulnerabilityIntegration(ScannerIntegration):
|
|
|
1940
1940
|
else:
|
|
1941
1941
|
logger.info("File %s does not exist. Fetching new data...", file_path)
|
|
1942
1942
|
|
|
1943
|
-
|
|
1944
|
-
|
|
1943
|
+
# Ensure we have a valid token (should already be set by caller)
|
|
1945
1944
|
if not self.wiz_token:
|
|
1946
|
-
error_and_exit("Wiz token is not set. Please authenticate
|
|
1945
|
+
error_and_exit("Wiz token is not set. Please authenticate before calling fetch_wiz_data_if_needed.")
|
|
1947
1946
|
|
|
1948
1947
|
nodes = fetch_wiz_data(
|
|
1949
1948
|
query=query,
|
|
@@ -1952,6 +1951,7 @@ class WizVulnerabilityIntegration(ScannerIntegration):
|
|
|
1952
1951
|
token=self.wiz_token,
|
|
1953
1952
|
topic_key=topic_key,
|
|
1954
1953
|
)
|
|
1954
|
+
|
|
1955
1955
|
with open(file_path, "w", encoding="utf-8") as file:
|
|
1956
1956
|
json.dump(nodes, file)
|
|
1957
1957
|
|