regscale-cli 6.20.10.0__py3-none-any.whl → 6.21.1.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 +12 -5
- regscale/core/app/internal/set_permissions.py +58 -27
- regscale/integrations/commercial/__init__.py +1 -2
- regscale/integrations/commercial/amazon/common.py +79 -2
- regscale/integrations/commercial/aws/cli.py +183 -9
- regscale/integrations/commercial/aws/scanner.py +544 -9
- regscale/integrations/commercial/cpe.py +18 -1
- regscale/integrations/commercial/nessus/scanner.py +2 -0
- regscale/integrations/commercial/sonarcloud.py +35 -36
- regscale/integrations/commercial/synqly/ticketing.py +51 -0
- regscale/integrations/commercial/tenablev2/jsonl_scanner.py +2 -1
- regscale/integrations/commercial/wizv2/async_client.py +10 -3
- regscale/integrations/commercial/wizv2/click.py +102 -26
- regscale/integrations/commercial/wizv2/constants.py +249 -1
- regscale/integrations/commercial/wizv2/issue.py +2 -2
- regscale/integrations/commercial/wizv2/parsers.py +3 -2
- regscale/integrations/commercial/wizv2/policy_compliance.py +1858 -0
- regscale/integrations/commercial/wizv2/scanner.py +15 -21
- regscale/integrations/commercial/wizv2/utils.py +258 -85
- regscale/integrations/commercial/wizv2/variables.py +4 -3
- regscale/integrations/compliance_integration.py +1455 -0
- regscale/integrations/integration_override.py +15 -6
- regscale/integrations/public/fedramp/fedramp_five.py +1 -1
- regscale/integrations/public/fedramp/markdown_parser.py +7 -1
- regscale/integrations/scanner_integration.py +193 -37
- regscale/models/app_models/__init__.py +1 -0
- regscale/models/integration_models/amazon_models/inspector_scan.py +32 -57
- regscale/models/integration_models/aqua.py +92 -78
- regscale/models/integration_models/cisa_kev_data.json +117 -5
- regscale/models/integration_models/defenderimport.py +64 -59
- regscale/models/integration_models/ecr_models/ecr.py +100 -147
- regscale/models/integration_models/flat_file_importer/__init__.py +52 -38
- regscale/models/integration_models/ibm.py +29 -47
- regscale/models/integration_models/nexpose.py +156 -68
- regscale/models/integration_models/prisma.py +46 -66
- regscale/models/integration_models/qualys.py +99 -93
- regscale/models/integration_models/snyk.py +229 -158
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/veracode.py +15 -20
- regscale/{integrations/commercial/wizv2/models.py → models/integration_models/wizv2.py} +4 -12
- regscale/models/integration_models/xray.py +276 -82
- regscale/models/regscale_models/control_implementation.py +14 -12
- regscale/models/regscale_models/file.py +4 -0
- regscale/models/regscale_models/issue.py +123 -0
- regscale/models/regscale_models/milestone.py +1 -1
- regscale/models/regscale_models/rbac.py +22 -0
- regscale/models/regscale_models/regscale_model.py +4 -2
- regscale/models/regscale_models/security_plan.py +1 -1
- regscale/utils/graphql_client.py +3 -1
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.1.0.dist-info}/METADATA +9 -9
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.1.0.dist-info}/RECORD +64 -60
- tests/fixtures/test_fixture.py +58 -2
- tests/regscale/core/test_app.py +5 -3
- tests/regscale/core/test_version_regscale.py +5 -3
- tests/regscale/integrations/test_integration_mapping.py +522 -40
- tests/regscale/integrations/test_issue_due_date.py +1 -1
- tests/regscale/integrations/test_update_finding_dates.py +336 -0
- tests/regscale/integrations/test_wiz_policy_compliance_affected_controls.py +154 -0
- tests/regscale/models/test_asset.py +406 -50
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.1.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.1.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.1.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.20.10.0.dist-info → regscale_cli-6.21.1.0.dist-info}/top_level.txt +0 -0
|
@@ -7,7 +7,7 @@ import os
|
|
|
7
7
|
import re
|
|
8
8
|
from typing import Any, Dict, Iterator, List, Optional, Union, Tuple
|
|
9
9
|
|
|
10
|
-
from regscale.core.app.utils.app_utils import check_file_path, get_current_datetime
|
|
10
|
+
from regscale.core.app.utils.app_utils import check_file_path, get_current_datetime, error_and_exit
|
|
11
11
|
from regscale.core.utils import get_base_protocol_from_port
|
|
12
12
|
from regscale.core.utils.date import format_to_regscale_iso
|
|
13
13
|
from regscale.integrations.commercial.wizv2.async_client import run_async_queries
|
|
@@ -46,7 +46,6 @@ from regscale.integrations.scanner_integration import IntegrationAsset, Integrat
|
|
|
46
46
|
from regscale.integrations.variables import ScannerVariables
|
|
47
47
|
from regscale.models import IssueStatus, regscale_models
|
|
48
48
|
from regscale.models.regscale_models.compliance_settings import ComplianceSettings
|
|
49
|
-
from regscale.core.app.utils.app_utils import error_and_exit
|
|
50
49
|
|
|
51
50
|
logger = logging.getLogger("regscale")
|
|
52
51
|
|
|
@@ -127,14 +126,14 @@ class WizVulnerabilityIntegration(ScannerIntegration):
|
|
|
127
126
|
# Use synchronous method if explicitly requested
|
|
128
127
|
yield from self.fetch_findings_sync(**kwargs)
|
|
129
128
|
|
|
130
|
-
|
|
131
|
-
def _validate_project_id(project_id: Optional[str]) -> str:
|
|
129
|
+
def _validate_project_id(self, project_id: Optional[str]) -> str:
|
|
132
130
|
"""
|
|
133
131
|
Validate and format the Wiz project ID.
|
|
134
132
|
|
|
135
133
|
:param Optional[str] project_id: Project ID to validate
|
|
136
134
|
:return: Validated project ID
|
|
137
135
|
:rtype: str
|
|
136
|
+
:raises ValueError: If project ID is invalid or missing
|
|
138
137
|
"""
|
|
139
138
|
if not project_id:
|
|
140
139
|
error_and_exit("Wiz project ID is required")
|
|
@@ -387,6 +386,7 @@ class WizVulnerabilityIntegration(ScannerIntegration):
|
|
|
387
386
|
:return: Results in the same format as async queries
|
|
388
387
|
:rtype: List[Tuple[str, List[Dict[str, Any]], Optional[Exception]]]
|
|
389
388
|
"""
|
|
389
|
+
|
|
390
390
|
results = []
|
|
391
391
|
cache_task = self.finding_progress.add_task("[green]Loading cached Wiz data...", total=len(query_configs))
|
|
392
392
|
|
|
@@ -1271,7 +1271,7 @@ class WizVulnerabilityIntegration(ScannerIntegration):
|
|
|
1271
1271
|
return filter_by
|
|
1272
1272
|
|
|
1273
1273
|
def get_software_details(
|
|
1274
|
-
self, wiz_entity_properties:
|
|
1274
|
+
self, wiz_entity_properties: dict, node: dict[str, Any], software_name_dict: dict[str, str], name: str
|
|
1275
1275
|
) -> Tuple[Optional[str], Optional[str], Optional[str]]:
|
|
1276
1276
|
"""
|
|
1277
1277
|
Gets the software version, vendor, and name from the Wiz entity properties and node.
|
|
@@ -1312,9 +1312,10 @@ class WizVulnerabilityIntegration(ScannerIntegration):
|
|
|
1312
1312
|
|
|
1313
1313
|
wiz_entity_properties = wiz_entity.get("properties", {})
|
|
1314
1314
|
is_public = False
|
|
1315
|
-
if public_exposures := wiz_entity.get("publicExposures")
|
|
1316
|
-
|
|
1317
|
-
|
|
1315
|
+
if (public_exposures := wiz_entity.get("publicExposures")) and (
|
|
1316
|
+
exposure_count := public_exposures.get("totalCount")
|
|
1317
|
+
):
|
|
1318
|
+
is_public = exposure_count > 0
|
|
1318
1319
|
|
|
1319
1320
|
network_dict = get_network_info(wiz_entity_properties)
|
|
1320
1321
|
handle_provider_dict = handle_provider(wiz_entity_properties)
|
|
@@ -1326,14 +1327,6 @@ class WizVulnerabilityIntegration(ScannerIntegration):
|
|
|
1326
1327
|
wiz_entity_properties, node, software_name_dict, name
|
|
1327
1328
|
)
|
|
1328
1329
|
|
|
1329
|
-
if WizVariables.useWizHardwareAssetTypes and node.get("graphEntity", {}).get("technologies", []):
|
|
1330
|
-
technologies = node.get("graphEntity", {}).get("technologies", [])
|
|
1331
|
-
deployment_models: set[str] = {
|
|
1332
|
-
tech.get("deploymentModel") for tech in technologies if tech.get("deploymentModel")
|
|
1333
|
-
}
|
|
1334
|
-
else:
|
|
1335
|
-
deployment_models = set()
|
|
1336
|
-
|
|
1337
1330
|
return IntegrationAsset(
|
|
1338
1331
|
name=name,
|
|
1339
1332
|
external_id=node.get("name"),
|
|
@@ -1344,7 +1337,7 @@ class WizVulnerabilityIntegration(ScannerIntegration):
|
|
|
1344
1337
|
asset_owner_id=ScannerVariables.userId,
|
|
1345
1338
|
parent_id=self.plan_id,
|
|
1346
1339
|
parent_module=regscale_models.SecurityPlan.get_module_slug(),
|
|
1347
|
-
asset_category=map_category(
|
|
1340
|
+
asset_category=map_category(node),
|
|
1348
1341
|
date_last_updated=wiz_entity.get("lastSeen", ""),
|
|
1349
1342
|
management_type=handle_management_type(wiz_entity_properties),
|
|
1350
1343
|
status=self.map_wiz_status(wiz_entity_properties.get("status")),
|
|
@@ -1419,7 +1412,8 @@ class WizVulnerabilityIntegration(ScannerIntegration):
|
|
|
1419
1412
|
:return: Software vendor
|
|
1420
1413
|
:rtype: Optional[str]
|
|
1421
1414
|
"""
|
|
1422
|
-
|
|
1415
|
+
|
|
1416
|
+
if map_category(node) == regscale_models.AssetCategory.Software:
|
|
1423
1417
|
return software_name_dict.get("software_vendor") or wiz_entity_properties.get("cloudPlatform")
|
|
1424
1418
|
return None
|
|
1425
1419
|
|
|
@@ -1433,8 +1427,8 @@ class WizVulnerabilityIntegration(ScannerIntegration):
|
|
|
1433
1427
|
:return: Software version
|
|
1434
1428
|
:rtype: Optional[str]
|
|
1435
1429
|
"""
|
|
1436
|
-
if map_category(node
|
|
1437
|
-
return handle_software_version(wiz_entity_properties,
|
|
1430
|
+
if map_category(node) == regscale_models.AssetCategory.Software:
|
|
1431
|
+
return handle_software_version(wiz_entity_properties, regscale_models.AssetCategory.Software) or "1.0"
|
|
1438
1432
|
return None
|
|
1439
1433
|
|
|
1440
1434
|
@staticmethod
|
|
@@ -1448,7 +1442,7 @@ class WizVulnerabilityIntegration(ScannerIntegration):
|
|
|
1448
1442
|
:return: Software name
|
|
1449
1443
|
:rtype: Optional[str]
|
|
1450
1444
|
"""
|
|
1451
|
-
if map_category(node
|
|
1445
|
+
if map_category(node) == regscale_models.AssetCategory.Software:
|
|
1452
1446
|
return software_name_dict.get("software_name") or wiz_entity_properties.get("nativeType")
|
|
1453
1447
|
return None
|
|
1454
1448
|
|
|
@@ -24,14 +24,15 @@ from regscale.core.app.utils.app_utils import (
|
|
|
24
24
|
error_and_exit,
|
|
25
25
|
check_file_path,
|
|
26
26
|
get_current_datetime,
|
|
27
|
-
format_dict_to_html,
|
|
28
27
|
create_progress_object,
|
|
29
28
|
)
|
|
30
29
|
from regscale.core.utils.date import datetime_obj
|
|
30
|
+
from regscale.integrations.commercial.cpe import extract_product_name_and_version
|
|
31
31
|
from regscale.integrations.commercial.wizv2.constants import (
|
|
32
32
|
BEARER,
|
|
33
33
|
CHECK_INTERVAL_FOR_DOWNLOAD_REPORT,
|
|
34
34
|
CONTENT_TYPE,
|
|
35
|
+
CPE_PART_TO_CATEGORY_MAPPING,
|
|
35
36
|
CREATE_REPORT_QUERY,
|
|
36
37
|
DOWNLOAD_QUERY,
|
|
37
38
|
MAX_RETRIES,
|
|
@@ -39,7 +40,7 @@ from regscale.integrations.commercial.wizv2.constants import (
|
|
|
39
40
|
REPORTS_QUERY,
|
|
40
41
|
RERUN_REPORT_QUERY,
|
|
41
42
|
)
|
|
42
|
-
from regscale.
|
|
43
|
+
from regscale.models.integration_models.wizv2 import ComplianceReport, ComplianceCheckStatus
|
|
43
44
|
from regscale.integrations.commercial.wizv2.variables import WizVariables
|
|
44
45
|
from regscale.integrations.commercial.wizv2.wiz_auth import wiz_authenticate
|
|
45
46
|
from regscale.models import (
|
|
@@ -151,36 +152,49 @@ def create_asset_type(asset_type: str) -> str:
|
|
|
151
152
|
return asset_type
|
|
152
153
|
|
|
153
154
|
|
|
154
|
-
def map_category(
|
|
155
|
+
def map_category(node: dict[str, Any]) -> regscale_models.AssetCategory:
|
|
155
156
|
"""
|
|
156
|
-
Map the asset category based on the
|
|
157
|
-
|
|
157
|
+
Map the asset category based on the given node. The node should be a CloudResoruce response from
|
|
158
|
+
the Wiz inventory query.
|
|
158
159
|
|
|
159
|
-
|
|
160
|
-
|
|
160
|
+
If the CloudResource type or any of the technologies deploymentModel entries match those in the
|
|
161
|
+
config parameter wizHardwareAssetTypes, "Hardware" will be returned, otherwise "Software".
|
|
162
|
+
|
|
163
|
+
:param dict[str, Any] node: A single node results from the Wiz CloudResource invenotty query.
|
|
164
|
+
It should have a 'type' key with the string asset type and a 'graphEntity' eky with a dict
|
|
165
|
+
which, in turn, has a 'technologies' key whose value is a dict with a 'deploymentModel' key
|
|
166
|
+
with a string value.
|
|
161
167
|
:return: RegScale AssetCategory
|
|
162
168
|
:rtype: regscale_models.AssetCategory
|
|
163
169
|
"""
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
if asset_string in WizVariables.wizHardwareAssetTypes:
|
|
170
|
+
|
|
171
|
+
# First check if there is a CPE which can tell us the category directly.
|
|
172
|
+
cpe = node.get("graphEntity", {}).get("properties", {}).get("cpe", "")
|
|
173
|
+
cpe_part = extract_product_name_and_version(cpe).get("part", "")
|
|
174
|
+
if cpe_part and cpe_part.lower() in CPE_PART_TO_CATEGORY_MAPPING:
|
|
175
|
+
return CPE_PART_TO_CATEGORY_MAPPING[cpe_part]
|
|
176
|
+
|
|
177
|
+
# Then try mapping by the configured Wiz hardware asset and technology deployment model types.
|
|
178
|
+
asset_type = node.get("type", "")
|
|
179
|
+
if WizVariables.useWizHardwareAssetTypes:
|
|
180
|
+
if asset_type in WizVariables.wizHardwareAssetTypes:
|
|
176
181
|
return regscale_models.AssetCategory.Hardware
|
|
177
|
-
|
|
182
|
+
if (graph_entity := node.get("graphEntity", {})) and (techs := graph_entity.get("technologies", [])):
|
|
183
|
+
for tech in techs:
|
|
184
|
+
# We double check just in case we get an explicit None for the technologies.
|
|
185
|
+
if tech and tech.get("deploymentModel", None) in WizVariables.wizHardwareAssetTypes:
|
|
186
|
+
return regscale_models.AssetCategory.Hardware
|
|
187
|
+
else:
|
|
188
|
+
logger.debug("No graphEntity set for node %r, default to Software.", node)
|
|
189
|
+
|
|
190
|
+
# Finally try matching the asset type directly by name.
|
|
191
|
+
if hasattr(regscale_models.AssetCategory, asset_type):
|
|
192
|
+
if asset_category := getattr(regscale_models.AssetCategory, asset_type):
|
|
178
193
|
return asset_category
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return regscale_models.AssetCategory.Software
|
|
194
|
+
logger.debug("Unknown AssetType %r for node %r. Defaulting to Software.", asset_type, node)
|
|
195
|
+
|
|
196
|
+
# If all else fails, default to software.
|
|
197
|
+
return regscale_models.AssetCategory.Software
|
|
184
198
|
|
|
185
199
|
|
|
186
200
|
def convert_first_seen_to_days(first_seen: str) -> int:
|
|
@@ -674,9 +688,44 @@ def create_compliance_report(
|
|
|
674
688
|
"type": "COMPLIANCE_ASSESSMENTS",
|
|
675
689
|
"csvDelimiter": "US",
|
|
676
690
|
"projectId": wiz_project_id,
|
|
677
|
-
"complianceAssessmentsParams": {
|
|
691
|
+
"complianceAssessmentsParams": {
|
|
692
|
+
"securityFrameworkIds": [framework_id],
|
|
693
|
+
},
|
|
678
694
|
"emailTargetParams": None,
|
|
679
695
|
"exportDestinations": None,
|
|
696
|
+
"columnSelection": [
|
|
697
|
+
"Assessed At",
|
|
698
|
+
"Category",
|
|
699
|
+
"Cloud Provider",
|
|
700
|
+
"Cloud Provider ID",
|
|
701
|
+
"Compliance Check Name (Wiz Subcategory)",
|
|
702
|
+
"Created At",
|
|
703
|
+
"Framework",
|
|
704
|
+
"Ignore Reason",
|
|
705
|
+
"Issue/Finding ID",
|
|
706
|
+
"Native Type",
|
|
707
|
+
"Object Type",
|
|
708
|
+
"Policy Description",
|
|
709
|
+
"Policy ID",
|
|
710
|
+
"Policy Name",
|
|
711
|
+
"Policy Short Name",
|
|
712
|
+
"Policy Type",
|
|
713
|
+
"Projects",
|
|
714
|
+
"Remediation Steps",
|
|
715
|
+
"Resource Cloud Platform",
|
|
716
|
+
"Resource Group Name",
|
|
717
|
+
"Resource ID",
|
|
718
|
+
"Resource Name",
|
|
719
|
+
"Resource Region",
|
|
720
|
+
"Result",
|
|
721
|
+
"Severity",
|
|
722
|
+
"Subscription",
|
|
723
|
+
"Subscription Name",
|
|
724
|
+
"Subscription Provider ID",
|
|
725
|
+
"Subscription Status",
|
|
726
|
+
"Tags",
|
|
727
|
+
"Updated At",
|
|
728
|
+
],
|
|
680
729
|
}
|
|
681
730
|
}
|
|
682
731
|
|
|
@@ -749,6 +798,8 @@ def rerun_expired_report(variables: Dict) -> requests.Response:
|
|
|
749
798
|
return response
|
|
750
799
|
|
|
751
800
|
|
|
801
|
+
# Compliance functions moved to wiz_compliance.py
|
|
802
|
+
# This is a deprecated function - use WizComplianceIntegration instead
|
|
752
803
|
def _sync_compliance(
|
|
753
804
|
wiz_project_id: str,
|
|
754
805
|
regscale_id: int,
|
|
@@ -757,6 +808,7 @@ def _sync_compliance(
|
|
|
757
808
|
client_secret: str,
|
|
758
809
|
catalog_id: Optional[int] = None,
|
|
759
810
|
framework: Optional[str] = "NIST800-53R5",
|
|
811
|
+
update_control_status: bool = True,
|
|
760
812
|
) -> List[ComplianceReport]:
|
|
761
813
|
"""
|
|
762
814
|
Sync compliance posture from Wiz to RegScale
|
|
@@ -768,6 +820,7 @@ def _sync_compliance(
|
|
|
768
820
|
:param str client_secret: Wiz Client Secret
|
|
769
821
|
:param Optional[int] catalog_id: Catalog ID, defaults to None
|
|
770
822
|
:param Optional[str] framework: Framework, defaults to NIST800-53R5
|
|
823
|
+
:param bool update_control_status: Update control implementation status based on compliance results, defaults to True
|
|
771
824
|
:return: List of ComplianceReport objects
|
|
772
825
|
:rtype: List[ComplianceReport]
|
|
773
826
|
"""
|
|
@@ -851,6 +904,7 @@ def _sync_compliance(
|
|
|
851
904
|
controls=controls,
|
|
852
905
|
progress=compliance_job_progress,
|
|
853
906
|
task=saving_regscale_data_job,
|
|
907
|
+
update_control_status=update_control_status,
|
|
854
908
|
)
|
|
855
909
|
logger.info("Completed saving RegScale data.")
|
|
856
910
|
except Exception:
|
|
@@ -922,7 +976,13 @@ def _clean_passing_list(passing: Dict, failing: Dict) -> None:
|
|
|
922
976
|
|
|
923
977
|
|
|
924
978
|
def create_assessment_from_compliance_report(
|
|
925
|
-
controls_to_reports: Dict,
|
|
979
|
+
controls_to_reports: Dict,
|
|
980
|
+
regscale_id: int,
|
|
981
|
+
regscale_module: str,
|
|
982
|
+
controls: List,
|
|
983
|
+
progress: Progress,
|
|
984
|
+
task: TaskID,
|
|
985
|
+
update_control_status: bool = True,
|
|
926
986
|
) -> None:
|
|
927
987
|
"""
|
|
928
988
|
Create assessment from compliance report
|
|
@@ -933,6 +993,7 @@ def create_assessment_from_compliance_report(
|
|
|
933
993
|
:param List controls: Controls
|
|
934
994
|
:param Progress progress: Progress object, used for progress bar updates
|
|
935
995
|
:param TaskID task: Task ID, used for progress bar updates
|
|
996
|
+
:param bool update_control_status: Update control implementation status based on compliance results, defaults to True
|
|
936
997
|
:return: None
|
|
937
998
|
:rtype: None
|
|
938
999
|
"""
|
|
@@ -954,7 +1015,12 @@ def create_assessment_from_compliance_report(
|
|
|
954
1015
|
filtered_results = [x for x in implementations if x.controlID == control_record_id]
|
|
955
1016
|
|
|
956
1017
|
start_time = time.time()
|
|
957
|
-
create_report_assessment(
|
|
1018
|
+
create_report_assessment(
|
|
1019
|
+
filtered_results,
|
|
1020
|
+
reports,
|
|
1021
|
+
control_id,
|
|
1022
|
+
update_control_status=update_control_status,
|
|
1023
|
+
)
|
|
958
1024
|
end_time = time.time()
|
|
959
1025
|
logger.debug(f"Assessment creation for {control_id} took {end_time - start_time:.2f} seconds")
|
|
960
1026
|
|
|
@@ -967,13 +1033,19 @@ def create_assessment_from_compliance_report(
|
|
|
967
1033
|
progress.update(task, advance=1)
|
|
968
1034
|
|
|
969
1035
|
|
|
970
|
-
def create_report_assessment(
|
|
1036
|
+
def create_report_assessment(
|
|
1037
|
+
filtered_results: List,
|
|
1038
|
+
reports: List,
|
|
1039
|
+
control_id: str,
|
|
1040
|
+
update_control_status: bool = True,
|
|
1041
|
+
) -> None:
|
|
971
1042
|
"""
|
|
972
1043
|
Create a single aggregated report assessment per control
|
|
973
1044
|
|
|
974
1045
|
:param List filtered_results: Filtered results
|
|
975
1046
|
:param List reports: List of ComplianceReport objects for this control
|
|
976
1047
|
:param str control_id: Control ID
|
|
1048
|
+
:param bool update_control_status: Update control implementation status based on compliance results, defaults to True
|
|
977
1049
|
:return: None
|
|
978
1050
|
:rtype: None
|
|
979
1051
|
"""
|
|
@@ -1042,11 +1114,15 @@ def create_report_assessment(filtered_results: List, reports: List, control_id:
|
|
|
1042
1114
|
isPublic=True,
|
|
1043
1115
|
).create()
|
|
1044
1116
|
|
|
1045
|
-
# Update implementation status once with aggregated result
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1117
|
+
# Update implementation status once with aggregated result (if enabled)
|
|
1118
|
+
if update_control_status:
|
|
1119
|
+
update_implementation_status(
|
|
1120
|
+
implementation=implementation,
|
|
1121
|
+
result=overall_result,
|
|
1122
|
+
)
|
|
1123
|
+
logger.debug(f"Updated implementation status for {control_id}: {overall_result}")
|
|
1124
|
+
else:
|
|
1125
|
+
logger.debug(f"Skipping implementation status update for {control_id} (disabled via parameter)")
|
|
1050
1126
|
|
|
1051
1127
|
logger.info(
|
|
1052
1128
|
f"Created aggregated assessment for {control_id}: {assessment.id} "
|
|
@@ -1188,76 +1264,173 @@ def report_result_to_implementation_status(result: str) -> str:
|
|
|
1188
1264
|
compliance_settings = get_wiz_compliance_settings()
|
|
1189
1265
|
|
|
1190
1266
|
if compliance_settings:
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1267
|
+
try:
|
|
1268
|
+
# Get implementation status labels from compliance settings
|
|
1269
|
+
status_labels = compliance_settings.get_field_labels("implementationStatus")
|
|
1270
|
+
|
|
1271
|
+
# Map compliance check result to implementation status
|
|
1272
|
+
result_lower = result.lower()
|
|
1273
|
+
for label in status_labels:
|
|
1274
|
+
label_lower = label.lower()
|
|
1275
|
+
if result_lower == ComplianceCheckStatus.PASS.value.lower():
|
|
1276
|
+
if label_lower in ["implemented", "complete", "compliant"]:
|
|
1277
|
+
return label
|
|
1278
|
+
elif result_lower == ComplianceCheckStatus.FAIL.value.lower():
|
|
1279
|
+
if label_lower in ["inremediation", "in remediation", "remediation", "failed", "non-compliant"]:
|
|
1280
|
+
return label
|
|
1281
|
+
else: # Not implemented or other status
|
|
1282
|
+
if label_lower in ["notimplemented", "not implemented", "pending", "planned"]:
|
|
1283
|
+
return label
|
|
1284
|
+
|
|
1285
|
+
logger.debug(f"No matching compliance setting found for result: {result}")
|
|
1286
|
+
except Exception as e:
|
|
1287
|
+
logger.debug(f"Error using compliance settings for implementation status mapping: {e}")
|
|
1194
1288
|
|
|
1195
1289
|
# Fallback to default mapping
|
|
1196
|
-
|
|
1290
|
+
if result == ComplianceCheckStatus.PASS.value:
|
|
1291
|
+
return ControlImplementationStatus.Implemented.value
|
|
1292
|
+
elif result == ComplianceCheckStatus.FAIL.value:
|
|
1293
|
+
return ControlImplementationStatus.InRemediation.value
|
|
1294
|
+
else:
|
|
1295
|
+
return ControlImplementationStatus.NotImplemented.value
|
|
1197
1296
|
|
|
1198
1297
|
|
|
1199
|
-
def
|
|
1298
|
+
def create_vulnerabilities_from_wiz_findings(
|
|
1299
|
+
wiz_project_id: str,
|
|
1300
|
+
regscale_plan_id: int,
|
|
1301
|
+
client_id: Optional[str] = None,
|
|
1302
|
+
client_secret: Optional[str] = None,
|
|
1303
|
+
filter_by_override: Optional[str] = None,
|
|
1304
|
+
) -> int:
|
|
1305
|
+
"""
|
|
1306
|
+
Create vulnerabilities from Wiz findings using the WizVulnerabilityIntegration class.
|
|
1307
|
+
|
|
1308
|
+
This function properly uses the ScannerIntegration framework to create vulnerabilities
|
|
1309
|
+
and associated mappings, following the established patterns.
|
|
1310
|
+
|
|
1311
|
+
:param str wiz_project_id: Wiz project ID to scan
|
|
1312
|
+
:param int regscale_plan_id: RegScale security plan ID
|
|
1313
|
+
:param Optional[str] client_id: Wiz client ID (optional, uses WizVariables if not provided)
|
|
1314
|
+
:param Optional[str] client_secret: Wiz client secret (optional, uses WizVariables if not provided)
|
|
1315
|
+
:param Optional[str] filter_by_override: Optional filter override for findings
|
|
1316
|
+
:return: Number of vulnerabilities processed
|
|
1317
|
+
:rtype: int
|
|
1200
1318
|
"""
|
|
1201
|
-
|
|
1319
|
+
# Import here to avoid circular imports
|
|
1320
|
+
from regscale.integrations.commercial.wizv2.scanner import WizVulnerabilityIntegration
|
|
1202
1321
|
|
|
1203
|
-
:param str result: Report result
|
|
1204
|
-
:param compliance_settings: Compliance settings object
|
|
1205
|
-
:return: Implementation status or None if not found
|
|
1206
|
-
:rtype: Optional[str]
|
|
1207
|
-
"""
|
|
1208
1322
|
try:
|
|
1209
|
-
|
|
1210
|
-
result_lower = result.lower()
|
|
1323
|
+
logger.info(f"Starting vulnerability creation for Wiz project {wiz_project_id}")
|
|
1211
1324
|
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
if status:
|
|
1215
|
-
return status
|
|
1325
|
+
# Create the integration instance
|
|
1326
|
+
wiz_integration = WizVulnerabilityIntegration(plan_id=regscale_plan_id)
|
|
1216
1327
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1328
|
+
# Authenticate if credentials provided
|
|
1329
|
+
if client_id and client_secret:
|
|
1330
|
+
wiz_integration.authenticate(client_id=client_id, client_secret=client_secret)
|
|
1331
|
+
elif not WizVariables.wizAccessToken:
|
|
1332
|
+
# Try to authenticate with stored credentials
|
|
1333
|
+
wiz_integration.authenticate()
|
|
1334
|
+
|
|
1335
|
+
# Set up any filter overrides
|
|
1336
|
+
if filter_by_override:
|
|
1337
|
+
try:
|
|
1338
|
+
filter_dict = json.loads(filter_by_override)
|
|
1339
|
+
logger.info(f"Using filter override: {filter_dict}")
|
|
1340
|
+
except json.JSONDecodeError:
|
|
1341
|
+
logger.warning(f"Invalid filter override JSON: {filter_by_override}")
|
|
1342
|
+
filter_dict = {}
|
|
1343
|
+
else:
|
|
1344
|
+
filter_dict = {"projectId": wiz_project_id}
|
|
1345
|
+
|
|
1346
|
+
# Use the sync_findings class method which handles the complete workflow:
|
|
1347
|
+
# 1. Creates ScanHistory
|
|
1348
|
+
# 2. Fetches findings from Wiz
|
|
1349
|
+
# 3. Creates vulnerabilities and vulnerability mappings
|
|
1350
|
+
# 4. Creates associated issues (if configured)
|
|
1351
|
+
# 5. Closes outdated vulnerabilities
|
|
1352
|
+
# 6. Updates scan history with results
|
|
1353
|
+
vulnerabilities_processed = WizVulnerabilityIntegration.sync_findings(
|
|
1354
|
+
plan_id=regscale_plan_id,
|
|
1355
|
+
wiz_project_id=wiz_project_id,
|
|
1356
|
+
filter_by_override=json.dumps(filter_dict) if filter_dict else None,
|
|
1357
|
+
)
|
|
1358
|
+
|
|
1359
|
+
logger.info(f"Successfully processed {vulnerabilities_processed} vulnerabilities from Wiz")
|
|
1360
|
+
return vulnerabilities_processed
|
|
1219
1361
|
|
|
1220
1362
|
except Exception as e:
|
|
1221
|
-
logger.
|
|
1222
|
-
|
|
1363
|
+
logger.error(f"Error creating vulnerabilities from Wiz findings: {e}", exc_info=True)
|
|
1364
|
+
raise
|
|
1223
1365
|
|
|
1224
1366
|
|
|
1225
|
-
def
|
|
1367
|
+
def create_single_vulnerability_from_wiz_data(
|
|
1368
|
+
wiz_finding_data: Dict[str, Any],
|
|
1369
|
+
asset_id: str,
|
|
1370
|
+
regscale_plan_id: int,
|
|
1371
|
+
scan_history_id: Optional[int] = None,
|
|
1372
|
+
) -> Optional[regscale_models.Vulnerability]:
|
|
1226
1373
|
"""
|
|
1227
|
-
|
|
1374
|
+
Create a single vulnerability from Wiz finding data.
|
|
1375
|
+
|
|
1376
|
+
This is a lower-level function for creating individual vulnerabilities when you have
|
|
1377
|
+
specific Wiz finding data and want more control over the process.
|
|
1228
1378
|
|
|
1229
|
-
:param str
|
|
1230
|
-
:param str
|
|
1231
|
-
:
|
|
1232
|
-
:
|
|
1379
|
+
:param Dict[str, Any] wiz_finding_data: Raw Wiz finding data
|
|
1380
|
+
:param str asset_id: Asset identifier for the vulnerability
|
|
1381
|
+
:param int regscale_plan_id: RegScale security plan ID
|
|
1382
|
+
:param Optional[int] scan_history_id: Scan history ID (creates new if not provided)
|
|
1383
|
+
:return: Created vulnerability or None if creation failed
|
|
1384
|
+
:rtype: Optional[regscale_models.Vulnerability]
|
|
1233
1385
|
"""
|
|
1234
|
-
|
|
1386
|
+
# Import here to avoid circular imports
|
|
1387
|
+
from regscale.integrations.commercial.wizv2.scanner import WizVulnerabilityIntegration
|
|
1388
|
+
from regscale.integrations.commercial.wizv2.constants import WizVulnerabilityType
|
|
1235
1389
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1390
|
+
try:
|
|
1391
|
+
# Create integration instance
|
|
1392
|
+
wiz_integration = WizVulnerabilityIntegration(plan_id=regscale_plan_id)
|
|
1393
|
+
|
|
1394
|
+
# Create or get scan history
|
|
1395
|
+
if scan_history_id:
|
|
1396
|
+
scan_history = regscale_models.ScanHistory.get_by_id(scan_history_id)
|
|
1397
|
+
if not scan_history:
|
|
1398
|
+
logger.error(f"Scan history with ID {scan_history_id} not found")
|
|
1399
|
+
return None
|
|
1400
|
+
else:
|
|
1401
|
+
scan_history = wiz_integration.create_scan_history()
|
|
1238
1402
|
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
label
|
|
1242
|
-
if label_lower in ["inremediation", "in remediation", "remediation", "failed", "non-compliant"]
|
|
1243
|
-
else None
|
|
1244
|
-
)
|
|
1403
|
+
# Parse the Wiz finding data into an IntegrationFinding
|
|
1404
|
+
integration_finding = wiz_integration.parse_finding(wiz_finding_data, WizVulnerabilityType.VULNERABILITY)
|
|
1245
1405
|
|
|
1246
|
-
|
|
1247
|
-
|
|
1406
|
+
if not integration_finding:
|
|
1407
|
+
logger.warning("Failed to parse Wiz finding data into IntegrationFinding")
|
|
1408
|
+
return None
|
|
1248
1409
|
|
|
1410
|
+
# Set the asset identifier
|
|
1411
|
+
integration_finding.asset_identifier = asset_id
|
|
1249
1412
|
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1413
|
+
# Get the asset
|
|
1414
|
+
asset = wiz_integration.get_asset_by_identifier(asset_id)
|
|
1415
|
+
if not asset:
|
|
1416
|
+
logger.error(f"Asset with identifier {asset_id} not found")
|
|
1417
|
+
return None
|
|
1253
1418
|
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1419
|
+
# Handle the vulnerability creation using the integration framework
|
|
1420
|
+
vulnerability_id = wiz_integration.handle_vulnerability(
|
|
1421
|
+
finding=integration_finding,
|
|
1422
|
+
asset=asset,
|
|
1423
|
+
scan_history=scan_history,
|
|
1424
|
+
)
|
|
1425
|
+
|
|
1426
|
+
if vulnerability_id:
|
|
1427
|
+
vulnerability = regscale_models.Vulnerability.get_by_id(vulnerability_id)
|
|
1428
|
+
logger.info(f"Successfully created vulnerability {vulnerability_id}")
|
|
1429
|
+
return vulnerability
|
|
1430
|
+
else:
|
|
1431
|
+
logger.warning("Failed to create vulnerability")
|
|
1432
|
+
return None
|
|
1433
|
+
|
|
1434
|
+
except Exception as e:
|
|
1435
|
+
logger.error(f"Error creating single vulnerability: {e}", exc_info=True)
|
|
1436
|
+
return None
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"""Wiz Variables"""
|
|
4
4
|
|
|
5
5
|
from regscale.core.app.utils.variables import RsVariableType, RsVariablesMeta
|
|
6
|
-
from regscale.integrations.commercial.wizv2.constants import RECOMMENDED_WIZ_INVENTORY_TYPES
|
|
6
|
+
from regscale.integrations.commercial.wizv2.constants import RECOMMENDED_WIZ_INVENTORY_TYPES, DEFAULT_WIZ_HARDWARE_TYPES
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class WizVariables(metaclass=RsVariablesMeta):
|
|
@@ -32,8 +32,9 @@ class WizVariables(metaclass=RsVariablesMeta):
|
|
|
32
32
|
useWizHardwareAssetTypes: RsVariableType(bool, False, required=False) # type: ignore
|
|
33
33
|
wizHardwareAssetTypes: RsVariableType(
|
|
34
34
|
list,
|
|
35
|
-
'["
|
|
36
|
-
|
|
35
|
+
'["CONTAINER", "CONTAINER_IMAGE", "VIRTUAL_MACHINE", "VIRTUAL_MACHINE_IMAGE", "DB_SERVER", '
|
|
36
|
+
'"CLIENT_APPLICATION", "SERVER_APPLICATION", "VIRTUAL_APPLIANCE"]',
|
|
37
|
+
default=DEFAULT_WIZ_HARDWARE_TYPES,
|
|
37
38
|
required=False,
|
|
38
39
|
) # type: ignore
|
|
39
40
|
wizReportAge: RsVariableType(int, "14", default=14, required=False) # type: ignore
|