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
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for Wiz status mapping functionality.
|
|
3
|
+
Tests that Wiz issue statuses are correctly mapped to RegScale IssueStatus.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from unittest.mock import Mock, patch
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from regscale.integrations.commercial.wizv2.issue import WizIssue
|
|
11
|
+
from regscale.integrations.commercial.wizv2.scanner import WizVulnerabilityIntegration
|
|
12
|
+
from regscale.models import IssueStatus
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestWizStatusMapping:
|
|
16
|
+
"""Test class for Wiz status mapping functionality."""
|
|
17
|
+
|
|
18
|
+
@pytest.fixture
|
|
19
|
+
def mock_app(self):
|
|
20
|
+
"""Create a mock app with config."""
|
|
21
|
+
app = Mock()
|
|
22
|
+
app.config = {}
|
|
23
|
+
return app
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def wiz_integration(self, mock_app):
|
|
27
|
+
"""Create a WizVulnerabilityIntegration instance."""
|
|
28
|
+
with patch("regscale.integrations.scanner_integration.ScannerIntegration.__init__"):
|
|
29
|
+
integration = WizVulnerabilityIntegration(app=mock_app, plan_id=1)
|
|
30
|
+
integration.app = mock_app
|
|
31
|
+
integration._compliance_settings = None
|
|
32
|
+
return integration
|
|
33
|
+
|
|
34
|
+
@pytest.fixture
|
|
35
|
+
def wiz_issue_integration(self, mock_app):
|
|
36
|
+
"""Create a WizIssue instance."""
|
|
37
|
+
with patch("regscale.integrations.scanner_integration.ScannerIntegration.__init__"):
|
|
38
|
+
integration = WizIssue(app=mock_app, plan_id=1)
|
|
39
|
+
integration.app = mock_app
|
|
40
|
+
integration._compliance_settings = None
|
|
41
|
+
return integration
|
|
42
|
+
|
|
43
|
+
def test_default_status_mapping_open_statuses(self, wiz_integration):
|
|
44
|
+
"""Test that OPEN and IN_PROGRESS statuses map to IssueStatus.Open."""
|
|
45
|
+
open_statuses = ["OPEN", "Open", "open", "IN_PROGRESS", "In_Progress", "in_progress"]
|
|
46
|
+
|
|
47
|
+
for status in open_statuses:
|
|
48
|
+
result = wiz_integration._get_default_issue_status_mapping(status)
|
|
49
|
+
assert result == IssueStatus.Open, f"Status '{status}' should map to Open, got {result}"
|
|
50
|
+
|
|
51
|
+
def test_default_status_mapping_closed_statuses(self, wiz_integration):
|
|
52
|
+
"""Test that RESOLVED and REJECTED statuses map to IssueStatus.Closed."""
|
|
53
|
+
closed_statuses = ["RESOLVED", "Resolved", "resolved", "REJECTED", "Rejected", "rejected"]
|
|
54
|
+
|
|
55
|
+
for status in closed_statuses:
|
|
56
|
+
result = wiz_integration._get_default_issue_status_mapping(status)
|
|
57
|
+
assert result == IssueStatus.Closed, f"Status '{status}' should map to Closed, got {result}"
|
|
58
|
+
|
|
59
|
+
def test_default_status_mapping_unknown_status(self, wiz_integration):
|
|
60
|
+
"""Test that unknown statuses default to IssueStatus.Open."""
|
|
61
|
+
unknown_statuses = ["UNKNOWN", "PENDING", "WEIRD_STATUS", ""]
|
|
62
|
+
|
|
63
|
+
for status in unknown_statuses:
|
|
64
|
+
result = wiz_integration._get_default_issue_status_mapping(status)
|
|
65
|
+
assert result == IssueStatus.Open, f"Unknown status '{status}' should default to Open, got {result}"
|
|
66
|
+
|
|
67
|
+
def test_map_status_to_issue_status_without_compliance_settings(self, wiz_integration):
|
|
68
|
+
"""Test status mapping without compliance settings (uses default mapping)."""
|
|
69
|
+
# Ensure no compliance settings
|
|
70
|
+
wiz_integration._compliance_settings = None
|
|
71
|
+
|
|
72
|
+
test_cases = [
|
|
73
|
+
("OPEN", IssueStatus.Open),
|
|
74
|
+
("IN_PROGRESS", IssueStatus.Open),
|
|
75
|
+
("RESOLVED", IssueStatus.Closed),
|
|
76
|
+
("REJECTED", IssueStatus.Closed),
|
|
77
|
+
("UNKNOWN", IssueStatus.Open),
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
for wiz_status, expected in test_cases:
|
|
81
|
+
result = wiz_integration.map_status_to_issue_status(wiz_status)
|
|
82
|
+
assert result == expected, f"Status '{wiz_status}' should map to {expected}, got {result}"
|
|
83
|
+
|
|
84
|
+
def test_map_status_to_issue_status_with_compliance_settings(self, wiz_integration):
|
|
85
|
+
"""Test status mapping with compliance settings."""
|
|
86
|
+
# Mock compliance settings
|
|
87
|
+
mock_compliance = Mock()
|
|
88
|
+
mock_compliance.get_field_labels.return_value = ["Open", "In Progress", "Closed", "Resolved"]
|
|
89
|
+
wiz_integration._compliance_settings = mock_compliance
|
|
90
|
+
|
|
91
|
+
# Test that IN_PROGRESS still maps to Open even with compliance settings
|
|
92
|
+
result = wiz_integration.map_status_to_issue_status("IN_PROGRESS")
|
|
93
|
+
assert result == IssueStatus.Open, "IN_PROGRESS should map to Open with compliance settings"
|
|
94
|
+
|
|
95
|
+
# Test other statuses
|
|
96
|
+
result = wiz_integration.map_status_to_issue_status("RESOLVED")
|
|
97
|
+
assert result == IssueStatus.Closed, "RESOLVED should map to Closed with compliance settings"
|
|
98
|
+
|
|
99
|
+
def test_match_wiz_status_to_label(self, wiz_integration):
|
|
100
|
+
"""Test the _match_wiz_status_to_label method."""
|
|
101
|
+
# Test open status mappings
|
|
102
|
+
assert wiz_integration._match_wiz_status_to_label("open", "Open") == IssueStatus.Open
|
|
103
|
+
assert wiz_integration._match_wiz_status_to_label("in_progress", "In Progress") == IssueStatus.Open
|
|
104
|
+
assert wiz_integration._match_wiz_status_to_label("in_progress", "Active") == IssueStatus.Open
|
|
105
|
+
|
|
106
|
+
# Test closed status mappings
|
|
107
|
+
assert wiz_integration._match_wiz_status_to_label("resolved", "Closed") == IssueStatus.Closed
|
|
108
|
+
assert wiz_integration._match_wiz_status_to_label("rejected", "Resolved") == IssueStatus.Closed
|
|
109
|
+
|
|
110
|
+
# Test non-matching combinations
|
|
111
|
+
assert wiz_integration._match_wiz_status_to_label("open", "Closed") is None
|
|
112
|
+
assert wiz_integration._match_wiz_status_to_label("resolved", "Open") is None
|
|
113
|
+
|
|
114
|
+
def test_wiz_issue_status_mapping(self, wiz_issue_integration):
|
|
115
|
+
"""Test that WizIssue class uses the same status mapping."""
|
|
116
|
+
test_cases = [
|
|
117
|
+
("OPEN", IssueStatus.Open),
|
|
118
|
+
("IN_PROGRESS", IssueStatus.Open),
|
|
119
|
+
("RESOLVED", IssueStatus.Closed),
|
|
120
|
+
("REJECTED", IssueStatus.Closed),
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
for wiz_status, expected in test_cases:
|
|
124
|
+
result = wiz_issue_integration.map_status_to_issue_status(wiz_status)
|
|
125
|
+
assert result == expected, f"WizIssue: Status '{wiz_status}' should map to {expected}, got {result}"
|
|
126
|
+
|
|
127
|
+
@pytest.mark.parametrize(
|
|
128
|
+
"status,expected",
|
|
129
|
+
[
|
|
130
|
+
("OPEN", IssueStatus.Open),
|
|
131
|
+
("Open", IssueStatus.Open),
|
|
132
|
+
("open", IssueStatus.Open),
|
|
133
|
+
("IN_PROGRESS", IssueStatus.Open),
|
|
134
|
+
("In_Progress", IssueStatus.Open),
|
|
135
|
+
("in_progress", IssueStatus.Open),
|
|
136
|
+
("RESOLVED", IssueStatus.Closed),
|
|
137
|
+
("Resolved", IssueStatus.Closed),
|
|
138
|
+
("resolved", IssueStatus.Closed),
|
|
139
|
+
("REJECTED", IssueStatus.Closed),
|
|
140
|
+
("Rejected", IssueStatus.Closed),
|
|
141
|
+
("rejected", IssueStatus.Closed),
|
|
142
|
+
("UNKNOWN_STATUS", IssueStatus.Open),
|
|
143
|
+
("", IssueStatus.Open),
|
|
144
|
+
],
|
|
145
|
+
)
|
|
146
|
+
def test_comprehensive_status_mapping(self, wiz_integration, status, expected):
|
|
147
|
+
"""Comprehensive parameterized test for all status variations."""
|
|
148
|
+
result = wiz_integration.map_status_to_issue_status(status)
|
|
149
|
+
assert result == expected, f"Status '{status}' should map to {expected}, got {result}"
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for WizVulnerabilityIntegration
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import unittest
|
|
7
|
+
from unittest.mock import patch, MagicMock
|
|
8
|
+
|
|
9
|
+
from regscale.core.app.utils.api_handler import APIHandler
|
|
10
|
+
from regscale.integrations.commercial.wizv2.scanner import WizVulnerabilityIntegration
|
|
11
|
+
from regscale.integrations.commercial.wizv2.variables import WizVariables
|
|
12
|
+
from regscale.integrations.variables import ScannerVariables
|
|
13
|
+
from regscale.models import regscale_models
|
|
14
|
+
from tests.regscale.integrations.commercial.wizv2 import (
|
|
15
|
+
asset_nodes,
|
|
16
|
+
vuln_nodes,
|
|
17
|
+
PROJECT_ID,
|
|
18
|
+
PLAN_ID,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger("regscale")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TestWizVulnerabilityIntegration(unittest.TestCase):
|
|
25
|
+
regscale_version = APIHandler().regscale_version
|
|
26
|
+
project_id = PROJECT_ID
|
|
27
|
+
plan_id = PLAN_ID
|
|
28
|
+
|
|
29
|
+
def clean_plan(self, plan_id):
|
|
30
|
+
if self.regscale_version >= "5.64.0" or self.regscale_version == "localdev":
|
|
31
|
+
for scan in regscale_models.ScanHistory.get_all_by_parent(
|
|
32
|
+
plan_id, regscale_models.SecurityPlan.get_module_string()
|
|
33
|
+
):
|
|
34
|
+
for vuln_mapping in regscale_models.VulnerabilityMapping.find_by_scan(scan.id):
|
|
35
|
+
vuln_mapping.delete()
|
|
36
|
+
# No delete api
|
|
37
|
+
# scan.delete()
|
|
38
|
+
for asset in regscale_models.Asset.get_all_by_parent(plan_id, regscale_models.SecurityPlan.get_module_string()):
|
|
39
|
+
for issue in regscale_models.Issue.get_all_by_parent(asset.id, asset.get_module_string()):
|
|
40
|
+
issue.delete()
|
|
41
|
+
asset.delete()
|
|
42
|
+
for issue in regscale_models.Issue.get_all_by_parent(plan_id, regscale_models.SecurityPlan.get_module_string()):
|
|
43
|
+
issue.delete()
|
|
44
|
+
|
|
45
|
+
def assert_vulnerability_counts(self, assets, expected_counts):
|
|
46
|
+
for asset in assets:
|
|
47
|
+
vulnerability_ids = self.get_vulnerability_ids(asset)
|
|
48
|
+
expected_count = expected_counts.get(asset.wizId, 0)
|
|
49
|
+
if expected_count != len(vulnerability_ids):
|
|
50
|
+
logger.error(f"Vulnerabilities for asset {asset.wizId}: {vulnerability_ids}")
|
|
51
|
+
self.assertEqual(
|
|
52
|
+
expected_count,
|
|
53
|
+
len(vulnerability_ids),
|
|
54
|
+
f"Expected {expected_count} vulnerability ids for asset {asset.wizId}, got {vulnerability_ids}",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def get_vulnerability_ids(self, asset):
|
|
58
|
+
if self.regscale_version >= "5.64.0" or self.regscale_version == "localdev":
|
|
59
|
+
return {
|
|
60
|
+
vuln_mapping.vulnerabilityId
|
|
61
|
+
for vuln_mapping in regscale_models.VulnerabilityMapping.find_by_asset(asset.id, status="Open")
|
|
62
|
+
}
|
|
63
|
+
return set()
|
|
64
|
+
|
|
65
|
+
def assert_open_issues_with_assets(self, assets, expected_count):
|
|
66
|
+
open_issues_with_assets = self.get_open_issues_with_assets(assets)
|
|
67
|
+
if expected_count != len(open_issues_with_assets):
|
|
68
|
+
logger.error(f"Open Issues: {open_issues_with_assets}")
|
|
69
|
+
self.assertEqual(
|
|
70
|
+
expected_count,
|
|
71
|
+
len(open_issues_with_assets),
|
|
72
|
+
f"Expected {expected_count} open issues tied to assets, but found {len(open_issues_with_assets)}",
|
|
73
|
+
)
|
|
74
|
+
self.verify_issue_asset_association(open_issues_with_assets, assets)
|
|
75
|
+
|
|
76
|
+
def get_open_issues_with_assets(self, assets):
|
|
77
|
+
open_issues = []
|
|
78
|
+
for asset in assets:
|
|
79
|
+
asset_issues = regscale_models.Issue.get_all_by_parent(
|
|
80
|
+
parent_id=asset.id, parent_module=asset.get_module_string()
|
|
81
|
+
)
|
|
82
|
+
open_issues.extend([issue for issue in asset_issues if issue.status == regscale_models.IssueStatus.Open])
|
|
83
|
+
return open_issues
|
|
84
|
+
|
|
85
|
+
def verify_issue_asset_association(self, issues, assets):
|
|
86
|
+
asset_names = [asset.wizId for asset in assets]
|
|
87
|
+
for issue in issues:
|
|
88
|
+
self.assertIsNotNone(issue.assetIdentifier, f"Issue {issue.id} is not associated with an asset")
|
|
89
|
+
self.assertIn(
|
|
90
|
+
issue.assetIdentifier.split("\n")[0],
|
|
91
|
+
asset_names,
|
|
92
|
+
f"Issue {issue.id} is associated with an asset not in the current set",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_wiz_data_if_needed")
|
|
96
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
97
|
+
def test_wiz_vulnerability_integration_consolidated(self, mock_authenticate, mock_fetch_wiz_data):
|
|
98
|
+
mock_authenticate.return_value = None
|
|
99
|
+
self.clean_plan(self.plan_id)
|
|
100
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
101
|
+
|
|
102
|
+
mock_fetch_wiz_data.return_value = asset_nodes
|
|
103
|
+
assets = list(integration.fetch_assets(wiz_project_id=self.project_id))
|
|
104
|
+
self.assertEqual(2, len(assets))
|
|
105
|
+
integration.sync_assets(plan_id=self.plan_id, wiz_project_id=self.project_id)
|
|
106
|
+
|
|
107
|
+
mock_fetch_wiz_data.return_value = vuln_nodes
|
|
108
|
+
findings = integration.fetch_findings(wiz_project_id=self.project_id)
|
|
109
|
+
self.assertEqual(12, len(list(findings)))
|
|
110
|
+
integration.sync_findings(plan_id=self.plan_id, wiz_project_id=self.project_id)
|
|
111
|
+
|
|
112
|
+
assets = regscale_models.Asset.get_all_by_parent(self.plan_id, regscale_models.SecurityPlan.get_module_string())
|
|
113
|
+
self.assertEqual(2, len(assets))
|
|
114
|
+
|
|
115
|
+
expected_counts = {
|
|
116
|
+
"52c50c20-3d07-58ac-ab2e-c412bf35351b": 2,
|
|
117
|
+
"52c50c20-3d07-58ac-ab2e-c412bf35351c": 1,
|
|
118
|
+
}
|
|
119
|
+
self.assert_vulnerability_counts(assets, expected_counts)
|
|
120
|
+
|
|
121
|
+
if self.regscale_version >= "5.64.0" or self.regscale_version == "localdev":
|
|
122
|
+
self.assert_open_issues_with_assets(assets, 2)
|
|
123
|
+
|
|
124
|
+
mock_fetch_wiz_data.return_value = vuln_nodes[:1]
|
|
125
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
126
|
+
integration.sync_findings(plan_id=self.plan_id, wiz_project_id=self.project_id)
|
|
127
|
+
|
|
128
|
+
expected_counts = {
|
|
129
|
+
"52c50c20-3d07-58ac-ab2e-c412bf35351b": 1,
|
|
130
|
+
"52c50c20-3d07-58ac-ab2e-c412bf35351c": 0,
|
|
131
|
+
}
|
|
132
|
+
self.assert_vulnerability_counts(assets, expected_counts)
|
|
133
|
+
|
|
134
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_wiz_data_if_needed")
|
|
135
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
136
|
+
def test_wiz_vulnerability_integration_per_asset(self, mock_authenticate, mock_fetch_wiz_data):
|
|
137
|
+
ScannerVariables.issueCreation = "PerAsset"
|
|
138
|
+
mock_authenticate.return_value = None
|
|
139
|
+
self.clean_plan(self.plan_id)
|
|
140
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
141
|
+
|
|
142
|
+
mock_fetch_wiz_data.return_value = asset_nodes
|
|
143
|
+
assets = list(integration.fetch_assets(wiz_project_id=self.project_id))
|
|
144
|
+
self.assertEqual(2, len(assets))
|
|
145
|
+
integration.sync_assets(plan_id=self.plan_id, wiz_project_id=self.project_id)
|
|
146
|
+
|
|
147
|
+
mock_fetch_wiz_data.return_value = vuln_nodes
|
|
148
|
+
findings = integration.fetch_findings(wiz_project_id=self.project_id)
|
|
149
|
+
self.assertEqual(12, len(list(findings)))
|
|
150
|
+
integration.sync_findings(plan_id=self.plan_id, wiz_project_id=self.project_id)
|
|
151
|
+
|
|
152
|
+
assets = regscale_models.Asset.get_all_by_parent(self.plan_id, regscale_models.SecurityPlan.get_module_string())
|
|
153
|
+
self.assertEqual(2, len(assets))
|
|
154
|
+
|
|
155
|
+
expected_counts = {
|
|
156
|
+
"52c50c20-3d07-58ac-ab2e-c412bf35351b": 2,
|
|
157
|
+
"52c50c20-3d07-58ac-ab2e-c412bf35351c": 1,
|
|
158
|
+
}
|
|
159
|
+
self.assert_vulnerability_counts(assets, expected_counts)
|
|
160
|
+
|
|
161
|
+
self.assert_open_issues_with_assets(assets, 3)
|
|
162
|
+
|
|
163
|
+
mock_fetch_wiz_data.return_value = vuln_nodes[:1]
|
|
164
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
165
|
+
integration.sync_findings(plan_id=self.plan_id, wiz_project_id=self.project_id)
|
|
166
|
+
|
|
167
|
+
expected_counts = {
|
|
168
|
+
"52c50c20-3d07-58ac-ab2e-c412bf35351b": 1,
|
|
169
|
+
"52c50c20-3d07-58ac-ab2e-c412bf35351c": 0,
|
|
170
|
+
}
|
|
171
|
+
self.assert_vulnerability_counts(assets, expected_counts)
|
|
172
|
+
|
|
173
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_wiz_data_if_needed")
|
|
174
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
175
|
+
def test_wiz_assets_with_hardware_asset_types_enabled(self, mock_authenticate, mock_fetch_wiz_data):
|
|
176
|
+
WizVariables.useWizHardwareAssetTypes = True
|
|
177
|
+
WizVariables.wizHardwareAssetTypes = ["CLIENT_APPLICATION"]
|
|
178
|
+
mock_authenticate.return_value = None
|
|
179
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
180
|
+
|
|
181
|
+
mock_fetch_wiz_data.return_value = asset_nodes
|
|
182
|
+
assets = list(integration.fetch_assets(wiz_project_id=self.project_id))
|
|
183
|
+
self.assertEqual(2, len(assets))
|
|
184
|
+
integration.sync_assets(plan_id=self.plan_id, wiz_project_id=self.project_id)
|
|
185
|
+
|
|
186
|
+
mock_fetch_wiz_data.return_value = vuln_nodes
|
|
187
|
+
findings = integration.fetch_findings(wiz_project_id=self.project_id)
|
|
188
|
+
self.assertEqual(12, len(list(findings)))
|
|
189
|
+
integration.sync_findings(plan_id=self.plan_id, wiz_project_id=self.project_id)
|
|
190
|
+
|
|
191
|
+
assets = regscale_models.Asset.get_all_by_parent(self.plan_id, regscale_models.SecurityPlan.get_module_string())
|
|
192
|
+
self.assertEqual(2, len(assets))
|
|
193
|
+
for asset in assets:
|
|
194
|
+
self.assertEqual(asset.assetCategory, regscale_models.AssetCategory.Hardware)
|
|
195
|
+
|
|
196
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_wiz_data_if_needed")
|
|
197
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
198
|
+
def test_wiz_assets_with_hardware_asset_types_disabled(self, mock_authenticate, mock_fetch_wiz_data):
|
|
199
|
+
WizVariables.useWizHardwareAssetTypes = False
|
|
200
|
+
WizVariables.wizHardwareAssetTypes = ["CLIENT_APPLICATION"]
|
|
201
|
+
mock_authenticate.return_value = None
|
|
202
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
203
|
+
|
|
204
|
+
mock_fetch_wiz_data.return_value = asset_nodes
|
|
205
|
+
assets = list(integration.fetch_assets(wiz_project_id=self.project_id))
|
|
206
|
+
self.assertEqual(2, len(assets))
|
|
207
|
+
integration.sync_assets(plan_id=self.plan_id, wiz_project_id=self.project_id)
|
|
208
|
+
|
|
209
|
+
mock_fetch_wiz_data.return_value = vuln_nodes
|
|
210
|
+
findings = integration.fetch_findings(wiz_project_id=self.project_id)
|
|
211
|
+
self.assertEqual(12, len(list(findings)))
|
|
212
|
+
integration.sync_findings(plan_id=self.plan_id, wiz_project_id=self.project_id)
|
|
213
|
+
|
|
214
|
+
assets = regscale_models.Asset.get_all_by_parent(self.plan_id, regscale_models.SecurityPlan.get_module_string())
|
|
215
|
+
self.assertEqual(2, len(assets))
|
|
216
|
+
for asset in assets:
|
|
217
|
+
self.assertEqual(asset.assetCategory, regscale_models.AssetCategory.Software)
|
|
218
|
+
|
|
219
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.fetch_wiz_data_if_needed")
|
|
220
|
+
@patch("regscale.integrations.commercial.wizv2.scanner.WizVulnerabilityIntegration.authenticate")
|
|
221
|
+
def test_wiz_due_date_calculation(self, mock_authenticate, mock_fetch_wiz_data):
|
|
222
|
+
from datetime import datetime, timedelta
|
|
223
|
+
from regscale.core.utils.date import date_obj
|
|
224
|
+
|
|
225
|
+
mock_authenticate.return_value = None
|
|
226
|
+
integration = WizVulnerabilityIntegration(plan_id=self.plan_id)
|
|
227
|
+
|
|
228
|
+
mock_fetch_wiz_data.return_value = vuln_nodes
|
|
229
|
+
mock_app = MagicMock()
|
|
230
|
+
mock_app.config = {"issues": {"wiz": {"critical": 1, "high": 2, "moderate": 3, "low": 4}}}
|
|
231
|
+
with patch.object(integration, "app", mock_app):
|
|
232
|
+
findings = integration.fetch_findings(wiz_project_id=self.project_id)
|
|
233
|
+
findings = list(findings)
|
|
234
|
+
self.assertEqual(12, len(findings))
|
|
235
|
+
for finding in findings:
|
|
236
|
+
# convert the due_date to a datetime object for comparison
|
|
237
|
+
finding_due_date = datetime.strptime(finding.due_date, "%Y-%m-%dT%H:%M:%S")
|
|
238
|
+
first_seen_date = date_obj(finding.first_seen)
|
|
239
|
+
if finding.severity == regscale_models.IssueSeverity.Critical.value:
|
|
240
|
+
self.assertEqual(
|
|
241
|
+
finding_due_date.date(),
|
|
242
|
+
(first_seen_date + timedelta(days=mock_app.config["issues"]["wiz"]["critical"])),
|
|
243
|
+
)
|
|
244
|
+
elif finding.severity == regscale_models.IssueSeverity.High.value:
|
|
245
|
+
self.assertEqual(
|
|
246
|
+
finding_due_date.date(),
|
|
247
|
+
(first_seen_date + timedelta(days=mock_app.config["issues"]["wiz"]["high"])),
|
|
248
|
+
)
|
|
249
|
+
elif finding.severity == regscale_models.IssueSeverity.Moderate.value:
|
|
250
|
+
self.assertEqual(
|
|
251
|
+
finding_due_date.date(),
|
|
252
|
+
(first_seen_date + timedelta(days=mock_app.config["issues"]["wiz"]["moderate"])),
|
|
253
|
+
)
|
|
254
|
+
elif finding.severity == regscale_models.IssueSeverity.Low.value:
|
|
255
|
+
self.assertEqual(
|
|
256
|
+
finding_due_date.date(),
|
|
257
|
+
(first_seen_date + timedelta(days=mock_app.config["issues"]["wiz"]["low"])),
|
|
258
|
+
)
|
|
259
|
+
else:
|
|
260
|
+
self.assertEqual(finding_due_date.date(), (first_seen_date + timedelta(days=60)))
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
if __name__ == "__main__":
|
|
264
|
+
unittest.main()
|