regscale-cli 6.26.0.0__py3-none-any.whl → 6.27.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/core/app/application.py +1 -1
- regscale/core/app/internal/evidence.py +419 -2
- regscale/dev/code_gen.py +24 -20
- regscale/integrations/commercial/jira.py +367 -126
- regscale/integrations/commercial/qualys/__init__.py +7 -8
- regscale/integrations/commercial/qualys/scanner.py +8 -3
- regscale/integrations/commercial/synqly/assets.py +17 -0
- regscale/integrations/commercial/synqly/vulnerabilities.py +45 -28
- regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
- regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
- regscale/integrations/commercial/tenablev2/commands.py +142 -1
- regscale/integrations/commercial/tenablev2/scanner.py +0 -1
- regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
- regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
- regscale/integrations/commercial/wizv2/click.py +44 -59
- regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
- regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
- regscale/integrations/commercial/wizv2/compliance_report.py +10 -9
- regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
- regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +3 -3
- regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +1 -17
- regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
- regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +5 -9
- regscale/integrations/commercial/wizv2/issue.py +1 -1
- regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
- regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
- regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
- regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
- regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
- regscale/integrations/commercial/wizv2/reports.py +1 -1
- regscale/integrations/commercial/wizv2/sbom.py +1 -1
- regscale/integrations/commercial/wizv2/scanner.py +40 -100
- regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
- regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
- regscale/integrations/commercial/wizv2/variables.py +89 -3
- regscale/integrations/compliance_integration.py +0 -46
- regscale/integrations/control_matcher.py +22 -3
- regscale/integrations/due_date_handler.py +14 -8
- regscale/integrations/public/fedramp/docx_parser.py +10 -1
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +393 -340
- regscale/integrations/public/fedramp/fedramp_five.py +1 -1
- regscale/integrations/scanner_integration.py +127 -57
- regscale/models/integration_models/cisa_kev_data.json +132 -9
- regscale/models/integration_models/qualys.py +3 -4
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +24 -7
- regscale/models/integration_models/synqly_models/synqly_model.py +8 -1
- regscale/models/regscale_models/control_implementation.py +1 -1
- regscale/models/regscale_models/issue.py +0 -1
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.0.dist-info}/METADATA +1 -17
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.0.dist-info}/RECORD +93 -60
- tests/regscale/integrations/commercial/test_jira.py +481 -91
- tests/regscale/integrations/commercial/test_wiz.py +96 -200
- tests/regscale/integrations/commercial/wizv2/__init__.py +1 -1
- tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
- tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
- tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
- tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
- tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
- tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
- tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
- tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
- tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
- tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
- tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
- tests/regscale/integrations/commercial/wizv2/test_issue.py +1 -1
- tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
- tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
- tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
- tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +1 -1
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +72 -29
- tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +946 -78
- tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +97 -202
- tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
- tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
- tests/regscale/integrations/public/test_fedramp.py +301 -0
- tests/regscale/integrations/test_control_matcher.py +83 -0
- regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3543
- tests/regscale/integrations/commercial/wizv2/test_wiz_policy_compliance.py +0 -750
- /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
"""Functions for parsing CIS benchmark output from Tenable"""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import re
|
|
5
|
+
from typing import Union, Optional
|
|
6
|
+
|
|
7
|
+
from regscale.core.app.utils.app_utils import get_current_datetime
|
|
8
|
+
from regscale.integrations.scanner_integration import IntegrationFinding, issue_due_date
|
|
9
|
+
from regscale.models import regscale_models
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger("regscale")
|
|
12
|
+
|
|
13
|
+
# Constants
|
|
14
|
+
_FAR_FUTURE_DATE = "2099-12-31T23:59:59Z" # Used for passed findings with no remediation needed
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def parse_cis_compliance_result(compliance_data: dict, finding: IntegrationFinding) -> IntegrationFinding:
|
|
18
|
+
"""
|
|
19
|
+
Parses CIS benchmark compliance data from Tenable and constructs an IntegrationFinding.
|
|
20
|
+
|
|
21
|
+
This function processes CIS benchmark compliance check results from either Tenable.io
|
|
22
|
+
compliance export API or Tenable Security Center analysis API, extracting key fields
|
|
23
|
+
and mapping them to RegScale checklist format.
|
|
24
|
+
|
|
25
|
+
:param dict compliance_data: The CIS compliance data dictionary from Tenable API
|
|
26
|
+
:param IntegrationFinding finding: The finding object to update with parsed data
|
|
27
|
+
:return: An IntegrationFinding object containing the parsed CIS compliance information
|
|
28
|
+
:rtype: IntegrationFinding
|
|
29
|
+
|
|
30
|
+
Example compliance_data structure (Tenable.io):
|
|
31
|
+
{
|
|
32
|
+
"check_id": "123456",
|
|
33
|
+
"check_name": "Ensure SSH Protocol is set to 2",
|
|
34
|
+
"audit_file": "CIS_AlmaLinux_OS_8_Server_v3.0.0_L1.audit",
|
|
35
|
+
"benchmark_name": "CIS AlmaLinux OS 8 Benchmark",
|
|
36
|
+
"benchmark_version": "v3.0.0",
|
|
37
|
+
"status": "FAILED",
|
|
38
|
+
"actual_value": "1",
|
|
39
|
+
"expected_value": "2",
|
|
40
|
+
"check_info": "Description of the check...",
|
|
41
|
+
"solution": "Edit /etc/ssh/sshd_config...",
|
|
42
|
+
"reference": "800-53|AC-17,CSCv7|9.2",
|
|
43
|
+
"see_also": "https://workbench.cisecurity.org/benchmarks/123",
|
|
44
|
+
"asset": {
|
|
45
|
+
"uuid": "abc-123-def",
|
|
46
|
+
"id": "456"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
# Extract basic CIS benchmark information
|
|
52
|
+
check_id = compliance_data.get("check_id", "")
|
|
53
|
+
check_name = compliance_data.get("check_name", "")
|
|
54
|
+
audit_file = compliance_data.get("audit_file", "")
|
|
55
|
+
benchmark_name = compliance_data.get("benchmark_name", "")
|
|
56
|
+
benchmark_version = compliance_data.get("benchmark_version", "")
|
|
57
|
+
status = compliance_data.get("status", "UNKNOWN")
|
|
58
|
+
|
|
59
|
+
# Extract check details
|
|
60
|
+
check_info = compliance_data.get("check_info", "")
|
|
61
|
+
solution = compliance_data.get("solution", "")
|
|
62
|
+
reference = compliance_data.get("reference", "")
|
|
63
|
+
see_also = compliance_data.get("see_also", "")
|
|
64
|
+
|
|
65
|
+
# Extract actual vs expected values
|
|
66
|
+
actual_value = compliance_data.get("actual_value", "")
|
|
67
|
+
expected_value = compliance_data.get("expected_value", "")
|
|
68
|
+
|
|
69
|
+
# Parse CIS level (1 or 2) from audit file name
|
|
70
|
+
# Example: "CIS_AlmaLinux_OS_8_Server_v3.0.0_L1.audit" -> Level 1
|
|
71
|
+
cis_level = _extract_cis_level(audit_file)
|
|
72
|
+
|
|
73
|
+
# Parse CIS benchmark ID from check_name or see_also URL
|
|
74
|
+
# Example: "1.1.1.1 Ensure mounting of cramfs filesystems is disabled"
|
|
75
|
+
cis_benchmark_id = _extract_cis_benchmark_id(check_name, see_also)
|
|
76
|
+
|
|
77
|
+
# Create descriptive title
|
|
78
|
+
title = f"CIS {cis_benchmark_id}: {check_name}" if cis_benchmark_id else check_name
|
|
79
|
+
issue_title = title
|
|
80
|
+
|
|
81
|
+
# Map status to checklist status
|
|
82
|
+
checklist_status = _map_cis_status_to_checklist(status)
|
|
83
|
+
|
|
84
|
+
# Map status to issue status (for tracking open/closed)
|
|
85
|
+
issue_status = (
|
|
86
|
+
regscale_models.IssueStatus.Open
|
|
87
|
+
if status in ["FAILED", "WARNING", "ERROR"]
|
|
88
|
+
else regscale_models.IssueStatus.Closed
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Map status to severity for failed checks
|
|
92
|
+
severity = _map_cis_status_to_severity(status, cis_level, check_name)
|
|
93
|
+
|
|
94
|
+
# Set priority based on severity
|
|
95
|
+
priority = severity.value.title() if severity else "Medium"
|
|
96
|
+
|
|
97
|
+
# Create comprehensive description
|
|
98
|
+
description = _create_cis_description(
|
|
99
|
+
check_info=check_info,
|
|
100
|
+
benchmark_name=benchmark_name,
|
|
101
|
+
benchmark_version=benchmark_version,
|
|
102
|
+
cis_level=cis_level,
|
|
103
|
+
actual_value=actual_value,
|
|
104
|
+
expected_value=expected_value,
|
|
105
|
+
solution=solution,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Create detailed results text
|
|
109
|
+
results = _create_cis_results_text(compliance_data, cis_level, cis_benchmark_id)
|
|
110
|
+
|
|
111
|
+
# Extract control references (e.g., NIST 800-53 controls)
|
|
112
|
+
control_references = _extract_control_references(reference)
|
|
113
|
+
|
|
114
|
+
# Set timestamps
|
|
115
|
+
current_datetime = get_current_datetime()
|
|
116
|
+
created_date = compliance_data.get("first_seen", current_datetime)
|
|
117
|
+
last_seen = compliance_data.get("last_seen", current_datetime)
|
|
118
|
+
|
|
119
|
+
# Calculate due date based on severity (skip for NotAssigned/passed findings)
|
|
120
|
+
if severity != regscale_models.IssueSeverity.NotAssigned:
|
|
121
|
+
due_date = issue_due_date(severity, created_date)
|
|
122
|
+
else:
|
|
123
|
+
# For passed findings, set a far future date since no remediation is needed
|
|
124
|
+
due_date = _FAR_FUTURE_DATE
|
|
125
|
+
|
|
126
|
+
# Update finding object
|
|
127
|
+
finding.title = title
|
|
128
|
+
finding.category = f"CIS Benchmark - Level {cis_level}"
|
|
129
|
+
finding.plugin_id = check_id
|
|
130
|
+
finding.plugin_name = check_name
|
|
131
|
+
finding.severity = severity
|
|
132
|
+
finding.description = description
|
|
133
|
+
finding.status = issue_status
|
|
134
|
+
finding.checklist_status = checklist_status
|
|
135
|
+
finding.priority = priority
|
|
136
|
+
finding.first_seen = created_date
|
|
137
|
+
finding.last_seen = last_seen
|
|
138
|
+
finding.issue_title = issue_title
|
|
139
|
+
finding.issue_type = "Risk"
|
|
140
|
+
finding.date_created = created_date
|
|
141
|
+
finding.date_last_updated = last_seen
|
|
142
|
+
finding.due_date = due_date
|
|
143
|
+
finding.external_id = f"CIS:{check_id}:{finding.asset_identifier}"
|
|
144
|
+
finding.recommendation_for_mitigation = solution
|
|
145
|
+
finding.results = results
|
|
146
|
+
finding.baseline = f"{benchmark_name} {benchmark_version}"
|
|
147
|
+
finding.vulnerability_number = cis_benchmark_id
|
|
148
|
+
finding.rule_id = cis_benchmark_id
|
|
149
|
+
finding.control_labels = control_references
|
|
150
|
+
finding.observations = f"Actual: {actual_value}, Expected: {expected_value}"
|
|
151
|
+
finding.gaps = check_info
|
|
152
|
+
finding.evidence = f"Status: {status}, Check ID: {check_id}"
|
|
153
|
+
finding.impact = f"CIS Level {cis_level} compliance impact"
|
|
154
|
+
|
|
155
|
+
return finding
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _extract_cis_level(audit_file: str) -> str:
|
|
159
|
+
"""
|
|
160
|
+
Extract CIS level (1 or 2) from audit file name.
|
|
161
|
+
|
|
162
|
+
:param str audit_file: The audit file name
|
|
163
|
+
:return: CIS level as string ("1" or "2"), defaults to "1" if not found
|
|
164
|
+
:rtype: str
|
|
165
|
+
|
|
166
|
+
Examples:
|
|
167
|
+
"CIS_AlmaLinux_OS_8_Server_v3.0.0_L1.audit" -> "1"
|
|
168
|
+
"CIS_Windows_Server_2019_v1.2.1_L2.audit" -> "2"
|
|
169
|
+
"""
|
|
170
|
+
match = re.search(r"_L([12])\.audit", audit_file, re.IGNORECASE)
|
|
171
|
+
return match.group(1) if match else "1"
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _extract_cis_benchmark_id(check_name: str, see_also: str) -> str:
|
|
175
|
+
"""
|
|
176
|
+
Extract CIS benchmark ID from check name or see_also URL.
|
|
177
|
+
|
|
178
|
+
:param str check_name: The check name
|
|
179
|
+
:param str see_also: The see_also URL
|
|
180
|
+
:return: CIS benchmark ID (e.g., "1.1.1.1") or empty string
|
|
181
|
+
:rtype: str
|
|
182
|
+
|
|
183
|
+
Examples:
|
|
184
|
+
"1.1.1.1 Ensure mounting of cramfs filesystems is disabled" -> "1.1.1.1"
|
|
185
|
+
"2.3.4 Ensure telnet client is not installed" -> "2.3.4"
|
|
186
|
+
"""
|
|
187
|
+
# Try to extract from check name (most common format)
|
|
188
|
+
if match := re.match(r"^([\d.]+)\s+", check_name):
|
|
189
|
+
return match.group(1)
|
|
190
|
+
|
|
191
|
+
# Try to extract from see_also URL
|
|
192
|
+
# Example: "https://workbench.cisecurity.org/benchmarks/123"
|
|
193
|
+
if see_also and "workbench.cisecurity.org" in see_also:
|
|
194
|
+
# Extract numeric ID from URL
|
|
195
|
+
if match := re.search(r"/benchmarks/(\d+)", see_also):
|
|
196
|
+
return match.group(1)
|
|
197
|
+
|
|
198
|
+
return ""
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _map_cis_status_to_checklist(status: str) -> regscale_models.ChecklistStatus:
|
|
202
|
+
"""
|
|
203
|
+
Map CIS compliance status to RegScale ChecklistStatus enum.
|
|
204
|
+
|
|
205
|
+
:param str status: The CIS compliance status
|
|
206
|
+
:return: RegScale ChecklistStatus enum value
|
|
207
|
+
:rtype: regscale_models.ChecklistStatus
|
|
208
|
+
|
|
209
|
+
Status Mapping:
|
|
210
|
+
PASSED -> PASS
|
|
211
|
+
FAILED -> FAIL
|
|
212
|
+
WARNING -> NOT_REVIEWED (manual check required)
|
|
213
|
+
ERROR -> FAIL
|
|
214
|
+
NOT_APPLICABLE -> NOT_APPLICABLE
|
|
215
|
+
"""
|
|
216
|
+
status_map = {
|
|
217
|
+
"PASSED": regscale_models.ChecklistStatus.PASS,
|
|
218
|
+
"FAILED": regscale_models.ChecklistStatus.FAIL,
|
|
219
|
+
"ERROR": regscale_models.ChecklistStatus.FAIL,
|
|
220
|
+
"WARNING": regscale_models.ChecklistStatus.NOT_REVIEWED, # Manual check required
|
|
221
|
+
"NOT_APPLICABLE": regscale_models.ChecklistStatus.NOT_APPLICABLE,
|
|
222
|
+
"NOT APPLICABLE": regscale_models.ChecklistStatus.NOT_APPLICABLE,
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
result_key = status.upper().replace("_", " ").strip()
|
|
226
|
+
if result_key not in status_map:
|
|
227
|
+
logger.warning(f"CIS status '{status}' not found in status map, defaulting to NOT_REVIEWED")
|
|
228
|
+
return regscale_models.ChecklistStatus.NOT_REVIEWED
|
|
229
|
+
|
|
230
|
+
return status_map[result_key]
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _map_cis_status_to_severity(status: str, cis_level: str, check_name: str) -> regscale_models.IssueSeverity:
|
|
234
|
+
"""
|
|
235
|
+
Map CIS compliance status and level to IssueSeverity enum.
|
|
236
|
+
|
|
237
|
+
CIS severity mapping follows Tenable conventions:
|
|
238
|
+
- PASSED: NotAssigned (Info level)
|
|
239
|
+
- FAILED: High for Level 1, Moderate for Level 2
|
|
240
|
+
- WARNING: Moderate (manual check)
|
|
241
|
+
- ERROR: High
|
|
242
|
+
|
|
243
|
+
:param str status: The CIS compliance status
|
|
244
|
+
:param str cis_level: The CIS level ("1" or "2")
|
|
245
|
+
:param str check_name: The check name (for additional context)
|
|
246
|
+
:return: IssueSeverity enum value
|
|
247
|
+
:rtype: regscale_models.IssueSeverity
|
|
248
|
+
"""
|
|
249
|
+
# Check for critical keywords in check name
|
|
250
|
+
critical_keywords = ["critical", "essential", "must", "required"]
|
|
251
|
+
is_critical = any(keyword in check_name.lower() for keyword in critical_keywords)
|
|
252
|
+
|
|
253
|
+
status_upper = status.upper()
|
|
254
|
+
|
|
255
|
+
if status_upper == "PASSED":
|
|
256
|
+
return regscale_models.IssueSeverity.NotAssigned
|
|
257
|
+
elif status_upper == "FAILED":
|
|
258
|
+
# Level 1 failures are generally more critical
|
|
259
|
+
if cis_level == "1" or is_critical:
|
|
260
|
+
return regscale_models.IssueSeverity.High
|
|
261
|
+
else:
|
|
262
|
+
return regscale_models.IssueSeverity.Moderate
|
|
263
|
+
elif status_upper == "WARNING":
|
|
264
|
+
# Manual checks are moderate severity
|
|
265
|
+
return regscale_models.IssueSeverity.Moderate
|
|
266
|
+
elif status_upper == "ERROR":
|
|
267
|
+
return regscale_models.IssueSeverity.High
|
|
268
|
+
else:
|
|
269
|
+
return regscale_models.IssueSeverity.Low
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _create_cis_description(
|
|
273
|
+
check_info: str,
|
|
274
|
+
benchmark_name: str,
|
|
275
|
+
benchmark_version: str,
|
|
276
|
+
cis_level: str,
|
|
277
|
+
actual_value: str,
|
|
278
|
+
expected_value: str,
|
|
279
|
+
solution: str,
|
|
280
|
+
) -> str:
|
|
281
|
+
"""
|
|
282
|
+
Create a comprehensive description for the CIS finding.
|
|
283
|
+
|
|
284
|
+
:param str check_info: The check information
|
|
285
|
+
:param str benchmark_name: The benchmark name
|
|
286
|
+
:param str benchmark_version: The benchmark version
|
|
287
|
+
:param str cis_level: The CIS level
|
|
288
|
+
:param str actual_value: The actual value found
|
|
289
|
+
:param str expected_value: The expected value
|
|
290
|
+
:param str solution: The solution/remediation
|
|
291
|
+
:return: Formatted description
|
|
292
|
+
:rtype: str
|
|
293
|
+
"""
|
|
294
|
+
description_parts = [
|
|
295
|
+
f"**CIS Benchmark**: {benchmark_name} {benchmark_version}",
|
|
296
|
+
f"**CIS Level**: {cis_level}",
|
|
297
|
+
"",
|
|
298
|
+
"**Check Information**:",
|
|
299
|
+
check_info or "No detailed information available.",
|
|
300
|
+
"",
|
|
301
|
+
"**Compliance Status**:",
|
|
302
|
+
f"- Expected Value: {expected_value or 'N/A'}",
|
|
303
|
+
f"- Actual Value: {actual_value or 'N/A'}",
|
|
304
|
+
]
|
|
305
|
+
|
|
306
|
+
if solution:
|
|
307
|
+
description_parts.extend(
|
|
308
|
+
[
|
|
309
|
+
"",
|
|
310
|
+
"**Remediation**:",
|
|
311
|
+
solution,
|
|
312
|
+
]
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
return "\n".join(description_parts)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _create_cis_results_text(compliance_data: dict, cis_level: str, cis_benchmark_id: str) -> str:
|
|
319
|
+
"""
|
|
320
|
+
Create detailed results text for the CIS finding.
|
|
321
|
+
|
|
322
|
+
:param dict compliance_data: The CIS compliance data dictionary
|
|
323
|
+
:param str cis_level: The CIS level
|
|
324
|
+
:param str cis_benchmark_id: The CIS benchmark ID
|
|
325
|
+
:return: Formatted results text
|
|
326
|
+
:rtype: str
|
|
327
|
+
"""
|
|
328
|
+
check_id = compliance_data.get("check_id", "")
|
|
329
|
+
check_name = compliance_data.get("check_name", "")
|
|
330
|
+
benchmark_name = compliance_data.get("benchmark_name", "")
|
|
331
|
+
benchmark_version = compliance_data.get("benchmark_version", "")
|
|
332
|
+
status = compliance_data.get("status", "UNKNOWN")
|
|
333
|
+
actual_value = compliance_data.get("actual_value", "")
|
|
334
|
+
expected_value = compliance_data.get("expected_value", "")
|
|
335
|
+
check_info = compliance_data.get("check_info", "")
|
|
336
|
+
solution = compliance_data.get("solution", "")
|
|
337
|
+
reference = compliance_data.get("reference", "")
|
|
338
|
+
|
|
339
|
+
results = (
|
|
340
|
+
f"CIS Benchmark ID: {cis_benchmark_id}, Status: {status}, Level: {cis_level}<br><br>"
|
|
341
|
+
f"Benchmark: {benchmark_name} {benchmark_version}<br><br>"
|
|
342
|
+
f"Check Name: {check_name}<br><br>"
|
|
343
|
+
f"Check Content: {check_info}<br><br>"
|
|
344
|
+
f"Expected Value: {expected_value}<br>"
|
|
345
|
+
f"Actual Value: {actual_value}<br><br>"
|
|
346
|
+
f"Fix Text: {solution}<br><br>"
|
|
347
|
+
f"References: {reference}<br>"
|
|
348
|
+
f"Check ID: {check_id}"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
return results
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _extract_control_references(reference: str) -> list:
|
|
355
|
+
"""
|
|
356
|
+
Extract control framework references from the reference string.
|
|
357
|
+
|
|
358
|
+
:param str reference: The reference string (e.g., "800-53|AC-17,CSCv7|9.2")
|
|
359
|
+
:return: List of control reference strings
|
|
360
|
+
:rtype: list
|
|
361
|
+
|
|
362
|
+
Example:
|
|
363
|
+
"800-53|AC-17,CSCv7|9.2,PCI-DSSv3.2.1|2.2.4"
|
|
364
|
+
-> ["800-53:AC-17", "CSCv7:9.2", "PCI-DSSv3.2.1:2.2.4"]
|
|
365
|
+
"""
|
|
366
|
+
if not reference:
|
|
367
|
+
return []
|
|
368
|
+
|
|
369
|
+
control_refs = []
|
|
370
|
+
|
|
371
|
+
# Split by comma to get individual framework references
|
|
372
|
+
ref_parts = reference.split(",")
|
|
373
|
+
|
|
374
|
+
for part in ref_parts:
|
|
375
|
+
# Split by pipe to get framework and control ID
|
|
376
|
+
if "|" in part:
|
|
377
|
+
framework, control_id = part.split("|", 1)
|
|
378
|
+
control_refs.append(f"{framework.strip()}:{control_id.strip()}")
|
|
379
|
+
|
|
380
|
+
return control_refs
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def parse_tenable_sc_cis_result(plugin_output: str, finding: IntegrationFinding) -> IntegrationFinding:
|
|
384
|
+
"""
|
|
385
|
+
Parse CIS benchmark results from Tenable Security Center plugin output.
|
|
386
|
+
|
|
387
|
+
This function handles the text-based plugin output format from Tenable SC
|
|
388
|
+
and extracts CIS benchmark compliance information.
|
|
389
|
+
|
|
390
|
+
:param str plugin_output: The plugin output text from Tenable SC
|
|
391
|
+
:param IntegrationFinding finding: The finding object to update
|
|
392
|
+
:return: Updated IntegrationFinding object
|
|
393
|
+
:rtype: IntegrationFinding
|
|
394
|
+
|
|
395
|
+
Example plugin_output format:
|
|
396
|
+
Check Name: 1.1.1.1: Ensure mounting of cramfs filesystems is disabled
|
|
397
|
+
Information: The cramfs filesystem...
|
|
398
|
+
Result: FAILED
|
|
399
|
+
Solution: Edit /etc/modprobe.d/...
|
|
400
|
+
Reference Information: Benchmark|CIS AlmaLinux OS 8 Benchmark v3.0.0 Level 1,Level|1
|
|
401
|
+
"""
|
|
402
|
+
# Extract fields using regex patterns (optimized to avoid catastrophic backtracking)
|
|
403
|
+
check_name = _extract_field(r"Check Name:\s*([^\n]+)", plugin_output, re.MULTILINE)
|
|
404
|
+
information = _extract_field(r"Information:\s*([^\n]+)", plugin_output, re.MULTILINE)
|
|
405
|
+
result = _extract_field(r"Result:\s*([^\n]+)", plugin_output, re.IGNORECASE | re.MULTILINE)
|
|
406
|
+
solution = _extract_field(r"Solution:\s*(.*?)(?=\n\nReference Information:)", plugin_output, re.DOTALL)
|
|
407
|
+
ref_info = _extract_field(r"Reference Information:\s*(.*)", plugin_output, re.DOTALL | re.MULTILINE)
|
|
408
|
+
|
|
409
|
+
# Parse reference information with error handling
|
|
410
|
+
ref_dict = {}
|
|
411
|
+
for item in ref_info.split(","):
|
|
412
|
+
if "|" in item:
|
|
413
|
+
parts = item.split("|", 1)
|
|
414
|
+
if len(parts) == 2:
|
|
415
|
+
ref_dict[parts[0].strip()] = parts[1].strip()
|
|
416
|
+
benchmark_info = ref_dict.get("Benchmark", "")
|
|
417
|
+
level_info = ref_dict.get("Level", "1")
|
|
418
|
+
|
|
419
|
+
# Create compliance data structure
|
|
420
|
+
compliance_data = {
|
|
421
|
+
"check_id": finding.plugin_id or "",
|
|
422
|
+
"check_name": check_name,
|
|
423
|
+
"audit_file": f"CIS_L{level_info}.audit",
|
|
424
|
+
"benchmark_name": benchmark_info,
|
|
425
|
+
"benchmark_version": "", # Not always available in SC output
|
|
426
|
+
"status": result.upper(),
|
|
427
|
+
"actual_value": "", # Not always in SC output
|
|
428
|
+
"expected_value": "", # Not always in SC output
|
|
429
|
+
"check_info": information,
|
|
430
|
+
"solution": solution,
|
|
431
|
+
"reference": ref_info,
|
|
432
|
+
"see_also": "",
|
|
433
|
+
"first_seen": finding.first_seen or get_current_datetime(),
|
|
434
|
+
"last_seen": finding.last_seen or get_current_datetime(),
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
# Parse using main CIS parser
|
|
438
|
+
return parse_cis_compliance_result(compliance_data, finding)
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _extract_field(pattern: str, text: str, flags: Union[int, re.RegexFlag] = 0, group: int = 1) -> str:
|
|
442
|
+
"""
|
|
443
|
+
Extract a field from a string using a regular expression.
|
|
444
|
+
|
|
445
|
+
:param str pattern: The regular expression pattern to search for
|
|
446
|
+
:param str text: The string to search in
|
|
447
|
+
:param Union[int, re.RegexFlag] flags: Optional regular expression flags
|
|
448
|
+
:param int group: The group number to return from the match
|
|
449
|
+
:return: The extracted field as a string, empty string if no match
|
|
450
|
+
:rtype: str
|
|
451
|
+
"""
|
|
452
|
+
match = re.search(pattern, text, flags)
|
|
453
|
+
return match.group(group).strip() if match else ""
|