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
|
@@ -27,6 +27,7 @@ from regscale.integrations.commercial.durosuite.process_devices import scan_duro
|
|
|
27
27
|
from regscale.integrations.commercial.durosuite.variables import DuroSuiteVariables
|
|
28
28
|
from regscale.integrations.commercial.stig_mapper_integration.mapping_engine import StigMappingEngine as STIGMapper
|
|
29
29
|
from regscale.integrations.due_date_handler import DueDateHandler
|
|
30
|
+
from regscale.integrations.milestone_manager import MilestoneManager
|
|
30
31
|
from regscale.integrations.public.cisa import pull_cisa_kev
|
|
31
32
|
from regscale.integrations.variables import ScannerVariables
|
|
32
33
|
from regscale.models import DateTimeEncoder, OpenIssueDict, Property, regscale_models
|
|
@@ -48,6 +49,31 @@ def get_thread_workers_max() -> int:
|
|
|
48
49
|
return ScannerVariables.threadMaxWorkers
|
|
49
50
|
|
|
50
51
|
|
|
52
|
+
def _create_config_override(
|
|
53
|
+
config: Optional[Dict[str, Dict]],
|
|
54
|
+
integration_name: str,
|
|
55
|
+
critical: Optional[int],
|
|
56
|
+
high: Optional[int],
|
|
57
|
+
moderate: Optional[int],
|
|
58
|
+
low: Optional[int],
|
|
59
|
+
) -> Dict[str, Dict]:
|
|
60
|
+
"""Create a config override for legacy parameter support."""
|
|
61
|
+
override_config = config.copy() if config else {}
|
|
62
|
+
if "issues" not in override_config:
|
|
63
|
+
override_config["issues"] = {}
|
|
64
|
+
if integration_name not in override_config["issues"]:
|
|
65
|
+
override_config["issues"][integration_name] = {}
|
|
66
|
+
|
|
67
|
+
integration_config = override_config["issues"][integration_name]
|
|
68
|
+
severity_params = {"critical": critical, "high": high, "moderate": moderate, "low": low}
|
|
69
|
+
|
|
70
|
+
for param_name, param_value in severity_params.items():
|
|
71
|
+
if param_value is not None:
|
|
72
|
+
integration_config[param_name] = param_value
|
|
73
|
+
|
|
74
|
+
return override_config
|
|
75
|
+
|
|
76
|
+
|
|
51
77
|
def issue_due_date(
|
|
52
78
|
severity: regscale_models.IssueSeverity,
|
|
53
79
|
created_date: str,
|
|
@@ -61,6 +87,9 @@ def issue_due_date(
|
|
|
61
87
|
"""
|
|
62
88
|
Calculate the due date for an issue based on its severity and creation date.
|
|
63
89
|
|
|
90
|
+
DEPRECATED: This function is kept for backward compatibility. New code should use DueDateHandler directly.
|
|
91
|
+
This function now uses DueDateHandler internally to ensure consistent behavior and proper validation.
|
|
92
|
+
|
|
64
93
|
:param regscale_models.IssueSeverity severity: The severity of the issue.
|
|
65
94
|
:param str created_date: The creation date of the issue.
|
|
66
95
|
:param Optional[int] critical: Days until due for high severity issues.
|
|
@@ -72,40 +101,19 @@ def issue_due_date(
|
|
|
72
101
|
:return: The due date for the issue.
|
|
73
102
|
:rtype: str
|
|
74
103
|
"""
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
regscale_models.IssueSeverity.Critical: critical,
|
|
89
|
-
regscale_models.IssueSeverity.High: high,
|
|
90
|
-
regscale_models.IssueSeverity.Moderate: moderate,
|
|
91
|
-
regscale_models.IssueSeverity.Low: low,
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if title and config:
|
|
95
|
-
# if title in a config key, use that key
|
|
96
|
-
issues_dict = config.get("issues", {})
|
|
97
|
-
matching_key = next((key.lower() for key in issues_dict if title.lower() in key.lower()), None)
|
|
98
|
-
if matching_key:
|
|
99
|
-
title_config = issues_dict.get(matching_key, {})
|
|
100
|
-
due_date_map = {
|
|
101
|
-
regscale_models.IssueSeverity.Critical: title_config.get("critical", critical),
|
|
102
|
-
regscale_models.IssueSeverity.High: title_config.get("high", high),
|
|
103
|
-
regscale_models.IssueSeverity.Moderate: title_config.get("moderate", moderate),
|
|
104
|
-
regscale_models.IssueSeverity.Low: title_config.get("low", low),
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
days = due_date_map.get(severity, low)
|
|
108
|
-
return date_str(get_day_increment(start=created_date, days=days))
|
|
104
|
+
integration_name = title or "default"
|
|
105
|
+
|
|
106
|
+
# Check if individual parameters need config override
|
|
107
|
+
if any(param is not None for param in [critical, high, moderate, low]):
|
|
108
|
+
config = _create_config_override(config, integration_name, critical, high, moderate, low)
|
|
109
|
+
|
|
110
|
+
due_date_handler = DueDateHandler(integration_name, config=config)
|
|
111
|
+
return due_date_handler.calculate_due_date(
|
|
112
|
+
severity=severity,
|
|
113
|
+
created_date=created_date,
|
|
114
|
+
cve=None, # Legacy function doesn't have CVE parameter
|
|
115
|
+
title=title,
|
|
116
|
+
)
|
|
109
117
|
|
|
110
118
|
|
|
111
119
|
class ManagedDefaultDict(Generic[K, V]):
|
|
@@ -665,6 +673,9 @@ class ScannerIntegration(ABC):
|
|
|
665
673
|
# Initialize due date handler for this integration
|
|
666
674
|
self.due_date_handler = DueDateHandler(self.title, config=self.app.config)
|
|
667
675
|
|
|
676
|
+
# Initialize milestone manager for this integration
|
|
677
|
+
self.milestone_manager = None # Lazy initialization after scan_date is set
|
|
678
|
+
|
|
668
679
|
if self.is_component:
|
|
669
680
|
self.component = regscale_models.Component.get_object(self.plan_id)
|
|
670
681
|
self.parent_module: str = regscale_models.Component.get_module_string()
|
|
@@ -730,6 +741,21 @@ class ScannerIntegration(ABC):
|
|
|
730
741
|
cls._lock_registry[key] = lock
|
|
731
742
|
return lock
|
|
732
743
|
|
|
744
|
+
def get_milestone_manager(self) -> MilestoneManager:
|
|
745
|
+
"""
|
|
746
|
+
Get or initialize the milestone manager.
|
|
747
|
+
|
|
748
|
+
:return: MilestoneManager instance
|
|
749
|
+
:rtype: MilestoneManager
|
|
750
|
+
"""
|
|
751
|
+
if self.milestone_manager is None:
|
|
752
|
+
self.milestone_manager = MilestoneManager(
|
|
753
|
+
integration_title=self.title,
|
|
754
|
+
assessor_id=self.assessor_id,
|
|
755
|
+
scan_date=self.scan_date or get_current_datetime(),
|
|
756
|
+
)
|
|
757
|
+
return self.milestone_manager
|
|
758
|
+
|
|
733
759
|
@staticmethod
|
|
734
760
|
def load_stig_mapper() -> Optional[STIGMapper]:
|
|
735
761
|
"""
|
|
@@ -990,15 +1016,18 @@ class ScannerIntegration(ABC):
|
|
|
990
1016
|
return res[:450]
|
|
991
1017
|
return prefix[:450]
|
|
992
1018
|
|
|
993
|
-
def get_or_create_assessment(
|
|
1019
|
+
def get_or_create_assessment(
|
|
1020
|
+
self, control_implementation_id: int, status: Optional[regscale_models.AssessmentResultsStatus] = None
|
|
1021
|
+
) -> regscale_models.Assessment:
|
|
994
1022
|
"""
|
|
995
|
-
Gets or creates a RegScale assessment
|
|
1023
|
+
Gets or creates a RegScale assessment.
|
|
996
1024
|
|
|
997
1025
|
:param int control_implementation_id: The ID of the control implementation
|
|
1026
|
+
:param Optional[regscale_models.AssessmentResultsStatus] status: Optional status override (used by cci_assessment)
|
|
998
1027
|
:return: The assessment
|
|
999
1028
|
:rtype: regscale_models.Assessment
|
|
1000
1029
|
"""
|
|
1001
|
-
logger.
|
|
1030
|
+
logger.debug("Getting or create assessment for control implementation %d", control_implementation_id)
|
|
1002
1031
|
assessment: Optional[regscale_models.Assessment] = self.assessment_map.get(control_implementation_id)
|
|
1003
1032
|
if assessment:
|
|
1004
1033
|
logger.debug(
|
|
@@ -1010,7 +1039,7 @@ class ScannerIntegration(ABC):
|
|
|
1010
1039
|
plannedStart=get_current_datetime(),
|
|
1011
1040
|
plannedFinish=get_current_datetime(),
|
|
1012
1041
|
status=regscale_models.AssessmentStatus.COMPLETE.value,
|
|
1013
|
-
assessmentResult=regscale_models.AssessmentResultsStatus.FAIL.value,
|
|
1042
|
+
assessmentResult=status.value if status else regscale_models.AssessmentResultsStatus.FAIL.value,
|
|
1014
1043
|
actualFinish=get_current_datetime(),
|
|
1015
1044
|
leadAssessorId=self.assessor_id,
|
|
1016
1045
|
parentId=control_implementation_id,
|
|
@@ -2106,7 +2135,9 @@ class ScannerIntegration(ABC):
|
|
|
2106
2135
|
|
|
2107
2136
|
def _set_issue_due_date(self, issue: regscale_models.Issue, finding: IntegrationFinding) -> None:
|
|
2108
2137
|
"""Set the due date for the issue using DueDateHandler."""
|
|
2138
|
+
# Always calculate or validate due date to ensure it's not in the past
|
|
2109
2139
|
if not finding.due_date:
|
|
2140
|
+
# No due date set, calculate new one
|
|
2110
2141
|
try:
|
|
2111
2142
|
base_created = finding.date_created or issue.dateCreated
|
|
2112
2143
|
finding.due_date = self.due_date_handler.calculate_due_date(
|
|
@@ -2125,6 +2156,12 @@ class ScannerIntegration(ABC):
|
|
|
2125
2156
|
cve=finding.cve,
|
|
2126
2157
|
title=finding.title or self.title,
|
|
2127
2158
|
)
|
|
2159
|
+
else:
|
|
2160
|
+
# Due date already exists, but validate it's not in the past (if noPastDueDates is enabled)
|
|
2161
|
+
finding.due_date = self.due_date_handler._ensure_future_due_date(
|
|
2162
|
+
finding.due_date, self.due_date_handler.integration_timelines.get(finding.severity, 60)
|
|
2163
|
+
)
|
|
2164
|
+
|
|
2128
2165
|
issue.dueDate = finding.due_date
|
|
2129
2166
|
|
|
2130
2167
|
def _set_additional_issue_fields(
|
|
@@ -2269,87 +2306,26 @@ class ScannerIntegration(ABC):
|
|
|
2269
2306
|
"""
|
|
2270
2307
|
Create milestones for an issue based on status transitions.
|
|
2271
2308
|
|
|
2309
|
+
Delegates to MilestoneManager for cleaner separation of concerns.
|
|
2310
|
+
Also ensures existing issues have creation milestones (backfills if missing).
|
|
2311
|
+
|
|
2272
2312
|
:param regscale_models.Issue issue: The issue to create milestones for
|
|
2273
2313
|
:param IntegrationFinding finding: The finding data
|
|
2274
2314
|
:param Optional[regscale_models.Issue] existing_issue: Existing issue for comparison
|
|
2275
2315
|
"""
|
|
2276
|
-
|
|
2277
|
-
return
|
|
2278
|
-
|
|
2279
|
-
if self._should_create_reopened_milestone(existing_issue, issue):
|
|
2280
|
-
self._create_milestone_safe(
|
|
2281
|
-
issue, finding, "Issue reopened from", get_current_datetime(), "reopened milestone"
|
|
2282
|
-
)
|
|
2283
|
-
elif self._should_create_closed_milestone(existing_issue, issue):
|
|
2284
|
-
self._create_milestone_safe(issue, finding, "Issue closed from", issue.dateCompleted, "closed milestone")
|
|
2285
|
-
elif not existing_issue:
|
|
2286
|
-
self._create_milestone_safe(issue, finding, "Issue created from", self.scan_date, "new issue milestone")
|
|
2287
|
-
else:
|
|
2288
|
-
logger.debug("No milestone created for issue %s from finding %s", issue.id, finding.external_id)
|
|
2316
|
+
milestone_manager = self.get_milestone_manager()
|
|
2289
2317
|
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
"""
|
|
2294
|
-
Check if a reopened milestone should be created.
|
|
2295
|
-
|
|
2296
|
-
:param Optional[regscale_models.Issue] existing_issue: The existing issue
|
|
2297
|
-
:param regscale_models.Issue issue: The current issue
|
|
2298
|
-
:return: True if reopened milestone should be created
|
|
2299
|
-
:rtype: bool
|
|
2300
|
-
"""
|
|
2301
|
-
return (
|
|
2302
|
-
existing_issue
|
|
2303
|
-
and existing_issue.status == regscale_models.IssueStatus.Closed
|
|
2304
|
-
and issue.status == regscale_models.IssueStatus.Open
|
|
2305
|
-
)
|
|
2306
|
-
|
|
2307
|
-
def _should_create_closed_milestone(
|
|
2308
|
-
self, existing_issue: Optional[regscale_models.Issue], issue: regscale_models.Issue
|
|
2309
|
-
) -> bool:
|
|
2310
|
-
"""
|
|
2311
|
-
Check if a closed milestone should be created.
|
|
2318
|
+
# For existing issues, ensure they have a creation milestone (backfill if missing)
|
|
2319
|
+
if existing_issue:
|
|
2320
|
+
milestone_manager.ensure_creation_milestone_exists(issue=issue, finding=finding)
|
|
2312
2321
|
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
return (
|
|
2319
|
-
existing_issue
|
|
2320
|
-
and existing_issue.status == regscale_models.IssueStatus.Open
|
|
2321
|
-
and issue.status == regscale_models.IssueStatus.Closed
|
|
2322
|
+
# Handle status transition milestones
|
|
2323
|
+
milestone_manager.create_milestones_for_issue(
|
|
2324
|
+
issue=issue,
|
|
2325
|
+
finding=finding,
|
|
2326
|
+
existing_issue=existing_issue,
|
|
2322
2327
|
)
|
|
2323
2328
|
|
|
2324
|
-
def _create_milestone_safe(
|
|
2325
|
-
self,
|
|
2326
|
-
issue: regscale_models.Issue,
|
|
2327
|
-
finding: IntegrationFinding,
|
|
2328
|
-
title_prefix: str,
|
|
2329
|
-
milestone_date: str,
|
|
2330
|
-
milestone_type: str,
|
|
2331
|
-
) -> None:
|
|
2332
|
-
"""
|
|
2333
|
-
Safely create a milestone with error handling.
|
|
2334
|
-
|
|
2335
|
-
:param regscale_models.Issue issue: The issue to create milestone for
|
|
2336
|
-
:param IntegrationFinding finding: The finding data
|
|
2337
|
-
:param str title_prefix: Prefix for milestone title
|
|
2338
|
-
:param str milestone_date: Date for the milestone
|
|
2339
|
-
:param str milestone_type: Description for logging purposes
|
|
2340
|
-
"""
|
|
2341
|
-
try:
|
|
2342
|
-
regscale_models.Milestone(
|
|
2343
|
-
title=f"{title_prefix} {self.title} scan",
|
|
2344
|
-
milestoneDate=milestone_date,
|
|
2345
|
-
responsiblePersonId=self.assessor_id,
|
|
2346
|
-
parentID=issue.id,
|
|
2347
|
-
parentModule="issues",
|
|
2348
|
-
).create_or_update()
|
|
2349
|
-
logger.debug("Added milestone for issue %s from finding %s", issue.id, finding.external_id)
|
|
2350
|
-
except Exception as e:
|
|
2351
|
-
logger.warning("Failed to create %s: %s", milestone_type, str(e))
|
|
2352
|
-
|
|
2353
2329
|
@staticmethod
|
|
2354
2330
|
def extra_data_to_properties(finding: IntegrationFinding, issue_id: int) -> None:
|
|
2355
2331
|
"""
|
|
@@ -2515,6 +2491,8 @@ class ScannerIntegration(ABC):
|
|
|
2515
2491
|
if found_issue.controlImplementationIds:
|
|
2516
2492
|
for control_id in found_issue.controlImplementationIds:
|
|
2517
2493
|
self.update_control_implementation_status_after_close(control_id)
|
|
2494
|
+
# Update assessment status to reflect the control implementation status
|
|
2495
|
+
self.update_assessment_status_from_control_implementation(control_id)
|
|
2518
2496
|
|
|
2519
2497
|
def handle_failing_checklist(
|
|
2520
2498
|
self,
|
|
@@ -2539,11 +2517,13 @@ class ScannerIntegration(ABC):
|
|
|
2539
2517
|
if failing_objective.name.lower().startswith("cci-"):
|
|
2540
2518
|
implementation_id = self.get_control_implementation_id_for_cci(failing_objective.name)
|
|
2541
2519
|
else:
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2520
|
+
implementation_id = self._fallback_implementation_id(failing_objective)
|
|
2521
|
+
|
|
2522
|
+
if not implementation_id or implementation_id is None:
|
|
2523
|
+
logger.warning(
|
|
2524
|
+
"Could not map objective to a Control Implementation for objective #%i.", failing_objective.id
|
|
2525
|
+
)
|
|
2526
|
+
continue
|
|
2547
2527
|
|
|
2548
2528
|
failing_option = regscale_models.ImplementationOption(
|
|
2549
2529
|
name="Failed STIG",
|
|
@@ -2565,13 +2545,36 @@ class ScannerIntegration(ABC):
|
|
|
2565
2545
|
).create_or_update()
|
|
2566
2546
|
|
|
2567
2547
|
# Create assessment and control test result
|
|
2568
|
-
assessment = self.get_or_create_assessment(
|
|
2548
|
+
assessment = self.get_or_create_assessment(
|
|
2549
|
+
implementation_id, status=regscale_models.AssessmentResultsStatus.FAIL
|
|
2550
|
+
)
|
|
2569
2551
|
if implementation_id:
|
|
2570
2552
|
control_test = self.create_or_get_control_test(finding, implementation_id)
|
|
2571
2553
|
self.create_control_test_result(
|
|
2572
2554
|
finding, control_test, assessment, regscale_models.ControlTestResultStatus.FAIL
|
|
2573
2555
|
)
|
|
2574
2556
|
|
|
2557
|
+
def _fallback_implementation_id(self, objective: regscale_models.ControlObjective) -> Optional[int]:
|
|
2558
|
+
"""
|
|
2559
|
+
Fallback method to get control implementation ID from objective name if CCI mapping fails.
|
|
2560
|
+
|
|
2561
|
+
:param regscale_models.ControlObjective objective: The control objective
|
|
2562
|
+
:return: The control implementation ID if found, None otherwise
|
|
2563
|
+
:rtype: Optional[int]
|
|
2564
|
+
"""
|
|
2565
|
+
control_label = objective_to_control_dot(objective.name)
|
|
2566
|
+
if implementation_id := self.control_implementation_id_map.get(control_label):
|
|
2567
|
+
return implementation_id
|
|
2568
|
+
|
|
2569
|
+
if control_id := self.control_id_to_implementation_map.get(objective.securityControlId):
|
|
2570
|
+
if control_label := self.control_map.get(control_id):
|
|
2571
|
+
implementation_id = self.control_implementation_id_map.get(control_label)
|
|
2572
|
+
if not implementation_id:
|
|
2573
|
+
print("No dice.")
|
|
2574
|
+
return implementation_id
|
|
2575
|
+
logger.debug("Could not find fallback implementation ID for objective #%i", objective.id)
|
|
2576
|
+
return None
|
|
2577
|
+
|
|
2575
2578
|
def handle_passing_checklist(
|
|
2576
2579
|
self,
|
|
2577
2580
|
finding: IntegrationFinding,
|
|
@@ -2595,15 +2598,12 @@ class ScannerIntegration(ABC):
|
|
|
2595
2598
|
if passing_objective.name.lower().startswith("cci-"):
|
|
2596
2599
|
implementation_id = self.get_control_implementation_id_for_cci(passing_objective.name)
|
|
2597
2600
|
else:
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
# Skip if we couldn't determine the implementation ID
|
|
2605
|
-
if implementation_id is None:
|
|
2606
|
-
logger.warning("Could not determine implementation ID for objective %s", passing_objective.name)
|
|
2601
|
+
implementation_id = self._fallback_implementation_id(passing_objective)
|
|
2602
|
+
|
|
2603
|
+
if not implementation_id or implementation_id is None:
|
|
2604
|
+
logger.warning(
|
|
2605
|
+
"Could not map objective to a Control Implementation for objective #%i.", passing_objective.id
|
|
2606
|
+
)
|
|
2607
2607
|
continue
|
|
2608
2608
|
|
|
2609
2609
|
passing_option = regscale_models.ImplementationOption(
|
|
@@ -2626,7 +2626,9 @@ class ScannerIntegration(ABC):
|
|
|
2626
2626
|
).create_or_update()
|
|
2627
2627
|
|
|
2628
2628
|
# Create assessment and control test result
|
|
2629
|
-
assessment = self.get_or_create_assessment(
|
|
2629
|
+
assessment = self.get_or_create_assessment(
|
|
2630
|
+
implementation_id, status=regscale_models.AssessmentResultsStatus.PASS
|
|
2631
|
+
)
|
|
2630
2632
|
control_test = self.create_or_get_control_test(finding, implementation_id)
|
|
2631
2633
|
self.create_control_test_result(
|
|
2632
2634
|
finding, control_test, assessment, regscale_models.ControlTestResultStatus.PASS
|
|
@@ -2691,7 +2693,11 @@ class ScannerIntegration(ABC):
|
|
|
2691
2693
|
logger.error("2. Asset not found for identifier %s", finding.asset_identifier)
|
|
2692
2694
|
return 0
|
|
2693
2695
|
|
|
2694
|
-
tool =
|
|
2696
|
+
tool = (
|
|
2697
|
+
regscale_models.ChecklistTool.CISBenchmarks
|
|
2698
|
+
if "simp.cis" in str(finding.vulnerability_number).lower()
|
|
2699
|
+
else regscale_models.ChecklistTool.STIGs
|
|
2700
|
+
)
|
|
2695
2701
|
if finding.vulnerability_type == "Vulnerability Scan":
|
|
2696
2702
|
tool = regscale_models.ChecklistTool.VulnerabilityScanner
|
|
2697
2703
|
|
|
@@ -3366,6 +3372,8 @@ class ScannerIntegration(ABC):
|
|
|
3366
3372
|
|
|
3367
3373
|
for control_id in affected_control_ids:
|
|
3368
3374
|
self.update_control_implementation_status_after_close(control_id)
|
|
3375
|
+
# Update assessment status to reflect the control implementation status
|
|
3376
|
+
self.update_assessment_status_from_control_implementation(control_id)
|
|
3369
3377
|
|
|
3370
3378
|
(
|
|
3371
3379
|
logger.info("Closed %d outdated issues.", closed_count)
|
|
@@ -3468,9 +3476,70 @@ class ScannerIntegration(ABC):
|
|
|
3468
3476
|
if control_implementation.status != new_status:
|
|
3469
3477
|
control_implementation.status = new_status
|
|
3470
3478
|
self.control_implementation_map[control_id] = control_implementation.save()
|
|
3471
|
-
logger.
|
|
3479
|
+
logger.debug("Updated control implementation %d status to %s", control_id, new_status)
|
|
3480
|
+
|
|
3481
|
+
def update_assessment_status_from_control_implementation(self, control_implementation_id: int) -> None:
|
|
3482
|
+
"""
|
|
3483
|
+
Updates the assessment status based on the control implementation status.
|
|
3484
|
+
Treats the ControlImplementation status as the source of truth.
|
|
3485
|
+
|
|
3486
|
+
Sets assessment to PASS if ControlImplementation status is FULLY_IMPLEMENTED,
|
|
3487
|
+
otherwise sets it to FAIL.
|
|
3472
3488
|
|
|
3473
|
-
|
|
3489
|
+
This method should be called after update_control_implementation_status_after_close
|
|
3490
|
+
to ensure assessments reflect the final control implementation state.
|
|
3491
|
+
|
|
3492
|
+
:param int control_implementation_id: The ID of the control implementation
|
|
3493
|
+
:rtype: None
|
|
3494
|
+
"""
|
|
3495
|
+
# Get the cached assessment for this control implementation
|
|
3496
|
+
assessment = self.assessment_map.get(control_implementation_id)
|
|
3497
|
+
|
|
3498
|
+
if not assessment:
|
|
3499
|
+
logger.debug(
|
|
3500
|
+
"No assessment found in cache for control implementation %d, skipping assessment update",
|
|
3501
|
+
control_implementation_id,
|
|
3502
|
+
)
|
|
3503
|
+
return
|
|
3504
|
+
|
|
3505
|
+
# Get the control implementation to check its status
|
|
3506
|
+
control_implementation = self.control_implementation_map.get(
|
|
3507
|
+
control_implementation_id
|
|
3508
|
+
) or regscale_models.ControlImplementation.get_object(object_id=control_implementation_id)
|
|
3509
|
+
|
|
3510
|
+
if not control_implementation:
|
|
3511
|
+
logger.warning("Control implementation %d not found, cannot update assessment", control_implementation_id)
|
|
3512
|
+
return
|
|
3513
|
+
|
|
3514
|
+
# Determine assessment result based on control implementation status
|
|
3515
|
+
# Treat ControlImplementation status as the source of truth
|
|
3516
|
+
new_assessment_result = (
|
|
3517
|
+
regscale_models.AssessmentResultsStatus.PASS
|
|
3518
|
+
if control_implementation.status == regscale_models.ImplementationStatus.FULLY_IMPLEMENTED.value
|
|
3519
|
+
else regscale_models.AssessmentResultsStatus.FAIL
|
|
3520
|
+
)
|
|
3521
|
+
|
|
3522
|
+
# Only update if the status has changed
|
|
3523
|
+
if assessment.assessmentResult != new_assessment_result.value:
|
|
3524
|
+
assessment.assessmentResult = new_assessment_result.value
|
|
3525
|
+
assessment.save()
|
|
3526
|
+
logger.debug(
|
|
3527
|
+
"Updated assessment %d for control implementation %d: assessmentResult=%s (based on control status: %s)",
|
|
3528
|
+
assessment.id,
|
|
3529
|
+
control_implementation_id,
|
|
3530
|
+
new_assessment_result.value,
|
|
3531
|
+
control_implementation.status,
|
|
3532
|
+
)
|
|
3533
|
+
else:
|
|
3534
|
+
logger.debug(
|
|
3535
|
+
"Assessment %d already has correct status %s for control implementation %d",
|
|
3536
|
+
assessment.id,
|
|
3537
|
+
assessment.assessmentResult,
|
|
3538
|
+
control_implementation_id,
|
|
3539
|
+
)
|
|
3540
|
+
|
|
3541
|
+
@staticmethod
|
|
3542
|
+
def is_issue_protected_from_auto_close(issue: regscale_models.Issue) -> bool:
|
|
3474
3543
|
"""
|
|
3475
3544
|
Check if an issue is protected from automatic closure.
|
|
3476
3545
|
|