regscale-cli 6.25.1.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/airflow/hierarchy.py +2 -2
- regscale/core/app/application.py +19 -4
- regscale/core/app/internal/evidence.py +419 -2
- regscale/core/app/internal/login.py +0 -1
- regscale/core/app/utils/catalog_utils/common.py +1 -1
- 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/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/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 +64 -79
- 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 +161 -165
- 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 +39 -99
- 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 +60 -41
- regscale/integrations/control_matcher.py +377 -0
- regscale/integrations/due_date_handler.py +14 -8
- 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/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/public/fedramp/poam_export_v5.py +888 -0
- regscale/integrations/scanner_integration.py +277 -153
- regscale/models/integration_models/cisa_kev_data.json +282 -9
- regscale/models/integration_models/nexpose.py +36 -10
- 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/locking.py +12 -8
- regscale/models/platform.py +1 -2
- regscale/models/regscale_models/control_implementation.py +47 -22
- regscale/models/regscale_models/issue.py +256 -95
- regscale/models/regscale_models/milestone.py +1 -1
- regscale/models/regscale_models/regscale_model.py +6 -1
- regscale/templates/__init__.py +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/METADATA +1 -17
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/RECORD +145 -65
- 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 +2204 -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 +1365 -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/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_compliance_report_normalization.py +138 -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 +343 -0
- 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 +165 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +1394 -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_findings_comprehensive.py +364 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
- tests/regscale/integrations/commercial/wizv2/test_wiz_status_mapping.py +149 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2.py +1132 -0
- tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +519 -0
- 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/fedramp/__init__.py +1 -0
- tests/regscale/integrations/public/fedramp/test_poam_export_v5.py +1293 -0
- tests/regscale/integrations/public/test_fedramp.py +301 -0
- tests/regscale/integrations/test_control_matcher.py +1397 -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/integrations/commercial/wizv2/policy_compliance.py +0 -3543
- /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.25.1.0.dist-info → regscale_cli-6.27.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Tests for Microsoft Defender Scanner integration"""
|
|
4
|
+
from unittest.mock import MagicMock, patch
|
|
5
|
+
|
|
6
|
+
from regscale.integrations.commercial.microsoft_defender.defender_api import DefenderApi
|
|
7
|
+
from regscale.integrations.commercial.microsoft_defender.defender_scanner import DefenderScanner
|
|
8
|
+
from regscale.integrations.scanner_integration import IntegrationAsset, IntegrationFinding
|
|
9
|
+
from regscale.models import IssueSeverity
|
|
10
|
+
from tests import CLITestFixture
|
|
11
|
+
|
|
12
|
+
PATH = "regscale.integrations.commercial.microsoft_defender.defender_scanner"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestDefenderScanner(CLITestFixture):
|
|
16
|
+
|
|
17
|
+
def test_init(self):
|
|
18
|
+
"""Test init file and config"""
|
|
19
|
+
self.verify_config(
|
|
20
|
+
[
|
|
21
|
+
"azureCloudSubscriptionId",
|
|
22
|
+
]
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
@patch(f"{PATH}.DefenderApi")
|
|
26
|
+
def test_defender_scanner_init_default_system(self, mock_defender_api_class):
|
|
27
|
+
"""Test DefenderScanner initialization with default system"""
|
|
28
|
+
mock_api = MagicMock(spec=DefenderApi)
|
|
29
|
+
mock_defender_api_class.return_value = mock_api
|
|
30
|
+
|
|
31
|
+
scanner = DefenderScanner(plan_id=1, is_component=False)
|
|
32
|
+
|
|
33
|
+
assert scanner.system == "cloud"
|
|
34
|
+
assert scanner.title == "Microsoft Defender for Cloud"
|
|
35
|
+
assert scanner.asset_identifier_field == "otherTrackingNumber"
|
|
36
|
+
assert scanner.api == mock_api
|
|
37
|
+
mock_defender_api_class.assert_called_once_with(system="cloud")
|
|
38
|
+
|
|
39
|
+
@patch(f"{PATH}.DefenderApi")
|
|
40
|
+
def test_defender_scanner_init_custom_system(self, mock_defender_api_class):
|
|
41
|
+
"""Test DefenderScanner initialization with custom system"""
|
|
42
|
+
mock_api = MagicMock(spec=DefenderApi)
|
|
43
|
+
mock_defender_api_class.return_value = mock_api
|
|
44
|
+
|
|
45
|
+
scanner = DefenderScanner(system="365", plan_id=1, is_component=False)
|
|
46
|
+
|
|
47
|
+
assert scanner.system == "365"
|
|
48
|
+
assert scanner.api == mock_api
|
|
49
|
+
mock_defender_api_class.assert_called_once_with(system="365")
|
|
50
|
+
|
|
51
|
+
def test_finding_severity_map(self):
|
|
52
|
+
"""Test that severity mapping is correctly defined"""
|
|
53
|
+
scanner = DefenderScanner(plan_id=1, is_component=False)
|
|
54
|
+
|
|
55
|
+
expected_mapping = {
|
|
56
|
+
"Critical": IssueSeverity.Critical,
|
|
57
|
+
"High": IssueSeverity.High,
|
|
58
|
+
"Medium": IssueSeverity.Moderate,
|
|
59
|
+
"Low": IssueSeverity.Low,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
assert scanner.finding_severity_map == expected_mapping
|
|
63
|
+
|
|
64
|
+
@patch(f"{PATH}.DefenderApi")
|
|
65
|
+
def test_fetch_assets(self, mock_defender_api_class):
|
|
66
|
+
"""Test fetching assets from Defender"""
|
|
67
|
+
mock_api = MagicMock(spec=DefenderApi)
|
|
68
|
+
mock_api.config = {"azureCloudSubscriptionId": "test-subscription"}
|
|
69
|
+
mock_api.execute_resource_graph_query.return_value = [
|
|
70
|
+
{
|
|
71
|
+
"resourceId": "/subscriptions/test/vm1",
|
|
72
|
+
"resourceName": "test-vm",
|
|
73
|
+
"resourceType": "microsoft.compute/virtualmachines",
|
|
74
|
+
"resourceLocation": "eastus",
|
|
75
|
+
"resourceGroup": "test-rg",
|
|
76
|
+
"ipAddress": "10.0.0.1",
|
|
77
|
+
"properties": {"osProfile": {"computerName": "test-vm"}},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"resourceId": "/subscriptions/test/storage1",
|
|
81
|
+
"resourceName": "test-storage",
|
|
82
|
+
"resourceType": "microsoft.storage/storageaccounts",
|
|
83
|
+
"resourceLocation": "westus",
|
|
84
|
+
"resourceGroup": "test-rg",
|
|
85
|
+
"ipAddress": "",
|
|
86
|
+
"properties": {"primaryEndpoints": {"blob": "https://test.blob.core.windows.net"}},
|
|
87
|
+
},
|
|
88
|
+
]
|
|
89
|
+
mock_defender_api_class.return_value = mock_api
|
|
90
|
+
|
|
91
|
+
scanner = DefenderScanner(plan_id=1, is_component=False)
|
|
92
|
+
|
|
93
|
+
assets = list(scanner.fetch_assets())
|
|
94
|
+
|
|
95
|
+
assert len(assets) == 2
|
|
96
|
+
assert scanner.num_assets_to_process == 2
|
|
97
|
+
|
|
98
|
+
# Check first asset (VM)
|
|
99
|
+
vm_asset = assets[0]
|
|
100
|
+
assert isinstance(vm_asset, IntegrationAsset)
|
|
101
|
+
assert vm_asset.name == "test-vm"
|
|
102
|
+
assert vm_asset.other_tracking_number == "/subscriptions/test/vm1"
|
|
103
|
+
assert vm_asset.ip_address == "10.0.0.1"
|
|
104
|
+
|
|
105
|
+
# Check second asset (Storage)
|
|
106
|
+
storage_asset = assets[1]
|
|
107
|
+
assert isinstance(storage_asset, IntegrationAsset)
|
|
108
|
+
assert storage_asset.name == "test-storage"
|
|
109
|
+
assert storage_asset.other_tracking_number == "/subscriptions/test/storage1"
|
|
110
|
+
assert storage_asset.fqdn == "https://test.blob.core.windows.net"
|
|
111
|
+
|
|
112
|
+
@patch(f"{PATH}.DefenderApi")
|
|
113
|
+
def test_fetch_findings(self, mock_defender_api_class):
|
|
114
|
+
"""Test fetching findings"""
|
|
115
|
+
mock_api = MagicMock(spec=DefenderApi)
|
|
116
|
+
mock_defender_api_class.return_value = mock_api
|
|
117
|
+
|
|
118
|
+
scanner = DefenderScanner(plan_id=1, is_component=False)
|
|
119
|
+
|
|
120
|
+
mock_findings = [MagicMock(spec=IntegrationFinding), MagicMock(spec=IntegrationFinding)]
|
|
121
|
+
|
|
122
|
+
findings = list(scanner.fetch_findings(integration_findings=mock_findings))
|
|
123
|
+
|
|
124
|
+
assert len(findings) == 2
|
|
125
|
+
assert all(isinstance(f, IntegrationFinding) for f in findings)
|
|
126
|
+
|
|
127
|
+
@patch(f"{PATH}.DefenderApi")
|
|
128
|
+
def test_parse_asset_vm(self, mock_defender_api_class):
|
|
129
|
+
"""Test parsing a virtual machine asset"""
|
|
130
|
+
mock_api = MagicMock(spec=DefenderApi)
|
|
131
|
+
mock_defender_api_class.return_value = mock_api
|
|
132
|
+
|
|
133
|
+
scanner = DefenderScanner(plan_id=1, is_component=False)
|
|
134
|
+
|
|
135
|
+
defender_asset = {
|
|
136
|
+
"resourceId": "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.Compute/virtualMachines/test-vm",
|
|
137
|
+
"resourceName": "test-vm",
|
|
138
|
+
"resourceType": "microsoft.compute/virtualmachines",
|
|
139
|
+
"resourceLocation": "eastus",
|
|
140
|
+
"resourceGroup": "test-rg",
|
|
141
|
+
"ipAddress": "10.0.0.1",
|
|
142
|
+
"properties": {
|
|
143
|
+
"osProfile": {"computerName": "test-vm"},
|
|
144
|
+
"networkProfile": {"networkInterfaces": [{"id": "/subscriptions/test/nic1"}]},
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
asset = scanner.parse_asset(defender_asset)
|
|
149
|
+
|
|
150
|
+
assert isinstance(asset, IntegrationAsset)
|
|
151
|
+
assert asset.name == "test-vm"
|
|
152
|
+
assert (
|
|
153
|
+
asset.other_tracking_number
|
|
154
|
+
== "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.Compute/virtualMachines/test-vm"
|
|
155
|
+
)
|
|
156
|
+
assert (
|
|
157
|
+
asset.azure_identifier
|
|
158
|
+
== "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.Compute/virtualMachines/test-vm"
|
|
159
|
+
)
|
|
160
|
+
assert asset.ip_address == "10.0.0.1"
|
|
161
|
+
assert asset.is_virtual is True
|
|
162
|
+
assert asset.baseline_configuration == "Azure Hardening Guide"
|
|
163
|
+
assert "microsoft.compute/virtualmachines" in asset.component_names
|
|
164
|
+
|
|
165
|
+
@patch(f"{PATH}.DefenderApi")
|
|
166
|
+
def test_parse_asset_storage_account(self, mock_defender_api_class):
|
|
167
|
+
"""Test parsing a storage account asset"""
|
|
168
|
+
mock_api = MagicMock(spec=DefenderApi)
|
|
169
|
+
mock_defender_api_class.return_value = mock_api
|
|
170
|
+
|
|
171
|
+
scanner = DefenderScanner(plan_id=1, is_component=False)
|
|
172
|
+
|
|
173
|
+
defender_asset = {
|
|
174
|
+
"resourceId": "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.Storage/storageAccounts/teststorage",
|
|
175
|
+
"resourceName": "teststorage",
|
|
176
|
+
"resourceType": "microsoft.storage/storageaccounts",
|
|
177
|
+
"resourceLocation": "westus",
|
|
178
|
+
"resourceGroup": "test-rg",
|
|
179
|
+
"ipAddress": "",
|
|
180
|
+
"properties": {"primaryEndpoints": {"blob": "https://teststorage.blob.core.windows.net"}},
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
asset = scanner.parse_asset(defender_asset)
|
|
184
|
+
|
|
185
|
+
assert isinstance(asset, IntegrationAsset)
|
|
186
|
+
assert asset.name == "teststorage"
|
|
187
|
+
assert asset.fqdn == "https://teststorage.blob.core.windows.net"
|
|
188
|
+
assert asset.software_function == "Storage blob to house unstructured files uploaded to the platform"
|
|
189
|
+
assert not asset.is_public_facing # Storage account is not public facing by default
|
|
190
|
+
|
|
191
|
+
@patch(f"{PATH}.DefenderApi")
|
|
192
|
+
def test_parse_asset_cdn_profile(self, mock_defender_api_class):
|
|
193
|
+
"""Test parsing a CDN profile asset"""
|
|
194
|
+
mock_api = MagicMock(spec=DefenderApi)
|
|
195
|
+
mock_defender_api_class.return_value = mock_api
|
|
196
|
+
|
|
197
|
+
scanner = DefenderScanner(plan_id=1, is_component=False)
|
|
198
|
+
|
|
199
|
+
defender_asset = {
|
|
200
|
+
"resourceId": "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.Cdn/profiles/testcdn",
|
|
201
|
+
"resourceName": "testcdn",
|
|
202
|
+
"resourceType": "microsoft.cdn/profiles",
|
|
203
|
+
"resourceLocation": "global",
|
|
204
|
+
"resourceGroup": "test-rg",
|
|
205
|
+
"ipAddress": "",
|
|
206
|
+
"properties": {},
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
asset = scanner.parse_asset(defender_asset)
|
|
210
|
+
|
|
211
|
+
assert isinstance(asset, IntegrationAsset)
|
|
212
|
+
assert asset.name == "testcdn"
|
|
213
|
+
assert asset.is_public_facing is True # CDN profiles are public facing
|
|
214
|
+
assert asset.software_function.startswith("Monitoring and controlling inbound and outbound traffic")
|
|
215
|
+
|
|
216
|
+
@patch(f"{PATH}.DefenderApi")
|
|
217
|
+
def test_parse_asset_network_security_group(self, mock_defender_api_class):
|
|
218
|
+
"""Test parsing a network security group asset"""
|
|
219
|
+
mock_api = MagicMock(spec=DefenderApi)
|
|
220
|
+
mock_defender_api_class.return_value = mock_api
|
|
221
|
+
|
|
222
|
+
scanner = DefenderScanner(plan_id=1, is_component=False)
|
|
223
|
+
|
|
224
|
+
defender_asset = {
|
|
225
|
+
"resourceId": "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.Network/networkSecurityGroups/test-nsg",
|
|
226
|
+
"resourceName": "test-nsg",
|
|
227
|
+
"resourceType": "microsoft.network/networksecuritygroups",
|
|
228
|
+
"resourceLocation": "eastus",
|
|
229
|
+
"resourceGroup": "test-rg",
|
|
230
|
+
"ipAddress": "",
|
|
231
|
+
"properties": {"securityRules": [{"properties": {"destinationAddressPrefix": "10.0.0.0/24"}}]},
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
asset = scanner.parse_asset(defender_asset)
|
|
235
|
+
|
|
236
|
+
assert isinstance(asset, IntegrationAsset)
|
|
237
|
+
assert asset.name == "test-nsg"
|
|
238
|
+
assert asset.ip_address == "10.0.0.0/24"
|
|
239
|
+
assert asset.software_function == "Network protection for internal communications and load balancing"
|
|
240
|
+
|
|
241
|
+
@patch(f"{PATH}.DefenderApi")
|
|
242
|
+
def test_parse_asset_key_vault(self, mock_defender_api_class):
|
|
243
|
+
"""Test parsing a key vault asset"""
|
|
244
|
+
mock_api = MagicMock(spec=DefenderApi)
|
|
245
|
+
mock_defender_api_class.return_value = mock_api
|
|
246
|
+
|
|
247
|
+
scanner = DefenderScanner(plan_id=1, is_component=False)
|
|
248
|
+
|
|
249
|
+
defender_asset = {
|
|
250
|
+
"resourceId": "/subscriptions/test/resourceGroups/test-rg/providers/Microsoft.KeyVault/vaults/testkv",
|
|
251
|
+
"resourceName": "testkv",
|
|
252
|
+
"resourceType": "microsoft.keyvault/vaults",
|
|
253
|
+
"resourceLocation": "eastus",
|
|
254
|
+
"resourceGroup": "test-rg",
|
|
255
|
+
"ipAddress": "",
|
|
256
|
+
"properties": {"vaultUri": "https://testkv.vault.azure.net/"},
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
asset = scanner.parse_asset(defender_asset)
|
|
260
|
+
|
|
261
|
+
assert isinstance(asset, IntegrationAsset)
|
|
262
|
+
assert asset.name == "testkv"
|
|
263
|
+
assert asset.fqdn == "https://testkv.vault.azure.net/"
|
|
264
|
+
assert asset.software_function == "To securely store API keys, passwords, certificates, or cryptographic keys"
|
|
265
|
+
|
|
266
|
+
@patch(f"{PATH}.DefenderApi")
|
|
267
|
+
def test_parse_asset_with_component_parent(self, mock_defender_api_class):
|
|
268
|
+
"""Test parsing asset with component as parent"""
|
|
269
|
+
mock_api = MagicMock(spec=DefenderApi)
|
|
270
|
+
mock_defender_api_class.return_value = mock_api
|
|
271
|
+
|
|
272
|
+
scanner = DefenderScanner(plan_id=1, is_component=True)
|
|
273
|
+
|
|
274
|
+
defender_asset = {
|
|
275
|
+
"resourceId": "/subscriptions/test/vm1",
|
|
276
|
+
"resourceName": "test-vm",
|
|
277
|
+
"resourceType": "microsoft.compute/virtualmachines",
|
|
278
|
+
"resourceLocation": "eastus",
|
|
279
|
+
"resourceGroup": "test-rg",
|
|
280
|
+
"ipAddress": "10.0.0.1",
|
|
281
|
+
"properties": {},
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
with patch("regscale.models.regscale_models") as mock_models:
|
|
285
|
+
mock_models.Component.get_module_slug.return_value = "components"
|
|
286
|
+
mock_models.SecurityPlan.get_module_slug.return_value = "securityPlans"
|
|
287
|
+
|
|
288
|
+
asset = scanner.parse_asset(defender_asset)
|
|
289
|
+
|
|
290
|
+
assert asset.parent_module == "components"
|
|
291
|
+
mock_models.Component.get_module_slug.assert_called_once()
|
|
292
|
+
|
|
293
|
+
@patch(f"{PATH}.DefenderApi")
|
|
294
|
+
def test_parse_asset_with_security_plan_parent(self, mock_defender_api_class):
|
|
295
|
+
"""Test parsing asset with security plan as parent"""
|
|
296
|
+
mock_api = MagicMock(spec=DefenderApi)
|
|
297
|
+
mock_defender_api_class.return_value = mock_api
|
|
298
|
+
|
|
299
|
+
scanner = DefenderScanner(plan_id=1, is_component=False)
|
|
300
|
+
|
|
301
|
+
defender_asset = {
|
|
302
|
+
"resourceId": "/subscriptions/test/vm1",
|
|
303
|
+
"resourceName": "test-vm",
|
|
304
|
+
"resourceType": "microsoft.compute/virtualmachines",
|
|
305
|
+
"resourceLocation": "eastus",
|
|
306
|
+
"resourceGroup": "test-rg",
|
|
307
|
+
"ipAddress": "10.0.0.1",
|
|
308
|
+
"properties": {},
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
with patch("regscale.models.regscale_models") as mock_models:
|
|
312
|
+
mock_models.Component.get_module_slug.return_value = "components"
|
|
313
|
+
mock_models.SecurityPlan.get_module_slug.return_value = "securityPlans"
|
|
314
|
+
|
|
315
|
+
asset = scanner.parse_asset(defender_asset)
|
|
316
|
+
|
|
317
|
+
assert asset.parent_module == "securityPlans"
|
|
318
|
+
mock_models.SecurityPlan.get_module_slug.assert_called_once()
|
|
319
|
+
|
|
320
|
+
@patch(f"{PATH}.DefenderApi")
|
|
321
|
+
def test_parse_asset_empty_ip_mapping(self, mock_defender_api_class):
|
|
322
|
+
"""Test parsing asset when IP mapping fails due to IndexError"""
|
|
323
|
+
mock_api = MagicMock(spec=DefenderApi)
|
|
324
|
+
mock_defender_api_class.return_value = mock_api
|
|
325
|
+
|
|
326
|
+
scanner = DefenderScanner(plan_id=1, is_component=False)
|
|
327
|
+
|
|
328
|
+
defender_asset = {
|
|
329
|
+
"resourceId": "/subscriptions/test/vm1",
|
|
330
|
+
"resourceName": "test-vm",
|
|
331
|
+
"resourceType": "microsoft.network/networksecuritygroups",
|
|
332
|
+
"resourceLocation": "eastus",
|
|
333
|
+
"resourceGroup": "test-rg",
|
|
334
|
+
"ipAddress": "",
|
|
335
|
+
"properties": {"securityRules": []}, # Empty list will cause IndexError
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
asset = scanner.parse_asset(defender_asset)
|
|
339
|
+
|
|
340
|
+
assert isinstance(asset, IntegrationAsset)
|
|
341
|
+
assert asset.name == "test-vm"
|
|
342
|
+
assert asset.ip_address is None
|
|
343
|
+
|
|
344
|
+
@patch(f"{PATH}.DefenderApi")
|
|
345
|
+
def test_parse_asset_empty_fqdn_mapping(self, mock_defender_api_class):
|
|
346
|
+
"""Test parsing asset when FQDN mapping fails due to IndexError"""
|
|
347
|
+
mock_api = MagicMock(spec=DefenderApi)
|
|
348
|
+
mock_defender_api_class.return_value = mock_api
|
|
349
|
+
|
|
350
|
+
scanner = DefenderScanner(plan_id=1, is_component=False)
|
|
351
|
+
|
|
352
|
+
defender_asset = {
|
|
353
|
+
"resourceId": "/subscriptions/test/app1",
|
|
354
|
+
"resourceName": "test-app",
|
|
355
|
+
"resourceType": "microsoft.app/containerapps",
|
|
356
|
+
"resourceLocation": "eastus",
|
|
357
|
+
"resourceGroup": "test-rg",
|
|
358
|
+
"ipAddress": "",
|
|
359
|
+
"properties": {"configuration": {"ingress": {}}}, # Missing fqdn will cause IndexError in complex mapping
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
asset = scanner.parse_asset(defender_asset)
|
|
363
|
+
|
|
364
|
+
assert isinstance(asset, IntegrationAsset)
|
|
365
|
+
assert asset.name == "test-app"
|
|
366
|
+
# Should not have fqdn set since mapping failed
|
|
367
|
+
assert not hasattr(asset, "fqdn") or asset.fqdn is None
|
|
368
|
+
|
|
369
|
+
@patch(f"{PATH}.DefenderApi")
|
|
370
|
+
def test_parse_asset_with_custom_description(self, mock_defender_api_class):
|
|
371
|
+
"""Test parsing asset with custom description from properties"""
|
|
372
|
+
mock_api = MagicMock(spec=DefenderApi)
|
|
373
|
+
mock_defender_api_class.return_value = mock_api
|
|
374
|
+
|
|
375
|
+
scanner = DefenderScanner(plan_id=1, is_component=False)
|
|
376
|
+
|
|
377
|
+
defender_asset = {
|
|
378
|
+
"resourceId": "/subscriptions/test/custom1",
|
|
379
|
+
"resourceName": "test-custom",
|
|
380
|
+
"resourceType": "custom.provider/resources", # Unknown type
|
|
381
|
+
"resourceLocation": "eastus",
|
|
382
|
+
"resourceGroup": "test-rg",
|
|
383
|
+
"ipAddress": "",
|
|
384
|
+
"properties": {"description": "Custom resource description"},
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
with patch(f"{PATH}.generate_html_table_from_dict") as mock_generate_table:
|
|
388
|
+
mock_generate_table.return_value = "<table>Asset details</table>"
|
|
389
|
+
|
|
390
|
+
asset = scanner.parse_asset(defender_asset)
|
|
391
|
+
|
|
392
|
+
assert asset.software_function == "Custom resource description"
|
|
393
|
+
mock_generate_table.assert_called_once_with(defender_asset)
|
|
394
|
+
|
|
395
|
+
@patch(f"{PATH}.DefenderApi")
|
|
396
|
+
def test_parse_asset_afd_endpoints(self, mock_defender_api_class):
|
|
397
|
+
"""Test parsing AFD endpoints asset type"""
|
|
398
|
+
mock_api = MagicMock(spec=DefenderApi)
|
|
399
|
+
mock_defender_api_class.return_value = mock_api
|
|
400
|
+
|
|
401
|
+
scanner = DefenderScanner(plan_id=1, is_component=False)
|
|
402
|
+
|
|
403
|
+
defender_asset = {
|
|
404
|
+
"resourceId": "/subscriptions/test/afd1",
|
|
405
|
+
"resourceName": "test-afd",
|
|
406
|
+
"resourceType": "microsoft.cdn/profiles/afdendpoints",
|
|
407
|
+
"resourceLocation": "global",
|
|
408
|
+
"resourceGroup": "test-rg",
|
|
409
|
+
"ipAddress": "",
|
|
410
|
+
"properties": {"hostName": "test.azurefd.net"},
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
asset = scanner.parse_asset(defender_asset)
|
|
414
|
+
|
|
415
|
+
assert isinstance(asset, IntegrationAsset)
|
|
416
|
+
assert asset.name == "test-afd"
|
|
417
|
+
assert asset.is_public_facing is True # AFD endpoints are public facing
|
|
418
|
+
assert asset.fqdn == "test.azurefd.net"
|
|
419
|
+
assert asset.software_function == "Endpoint that all of the routes attach to"
|
|
420
|
+
|
|
421
|
+
@patch(f"{PATH}.DefenderApi")
|
|
422
|
+
def test_parse_asset_action_rules_not_authenticated(self, mock_defender_api_class):
|
|
423
|
+
"""Test parsing assets that are not authenticated scans"""
|
|
424
|
+
mock_api = MagicMock(spec=DefenderApi)
|
|
425
|
+
mock_defender_api_class.return_value = mock_api
|
|
426
|
+
|
|
427
|
+
scanner = DefenderScanner(plan_id=1, is_component=False)
|
|
428
|
+
|
|
429
|
+
# Test action rules
|
|
430
|
+
defender_asset = {
|
|
431
|
+
"resourceId": "/subscriptions/test/action1",
|
|
432
|
+
"resourceName": "test-action",
|
|
433
|
+
"resourceType": "microsoft.alertsmanagement/actionrules",
|
|
434
|
+
"resourceLocation": "global",
|
|
435
|
+
"resourceGroup": "test-rg",
|
|
436
|
+
"ipAddress": "",
|
|
437
|
+
"properties": {},
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
asset = scanner.parse_asset(defender_asset)
|
|
441
|
+
|
|
442
|
+
assert asset.is_authenticated_scan is False
|
|
443
|
+
|
|
444
|
+
@patch(f"{PATH}.DefenderApi")
|
|
445
|
+
def test_parse_asset_smart_detector_not_authenticated(self, mock_defender_api_class):
|
|
446
|
+
"""Test parsing smart detector alert rules - not authenticated scans"""
|
|
447
|
+
mock_api = MagicMock(spec=DefenderApi)
|
|
448
|
+
mock_defender_api_class.return_value = mock_api
|
|
449
|
+
|
|
450
|
+
scanner = DefenderScanner(plan_id=1, is_component=False)
|
|
451
|
+
|
|
452
|
+
defender_asset = {
|
|
453
|
+
"resourceId": "/subscriptions/test/smart1",
|
|
454
|
+
"resourceName": "test-smart",
|
|
455
|
+
"resourceType": "microsoft.alertsmanagement/smartdetectoralertrules",
|
|
456
|
+
"resourceLocation": "global",
|
|
457
|
+
"resourceGroup": "test-rg",
|
|
458
|
+
"ipAddress": "",
|
|
459
|
+
"properties": {},
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
asset = scanner.parse_asset(defender_asset)
|
|
463
|
+
|
|
464
|
+
assert asset.is_authenticated_scan is False
|
|
465
|
+
|
|
466
|
+
@patch(f"{PATH}.DefenderApi")
|
|
467
|
+
def test_parse_asset_dns_zone_name_as_fqdn(self, mock_defender_api_class):
|
|
468
|
+
"""Test parsing private DNS zone where name is used as FQDN"""
|
|
469
|
+
mock_api = MagicMock(spec=DefenderApi)
|
|
470
|
+
mock_defender_api_class.return_value = mock_api
|
|
471
|
+
|
|
472
|
+
scanner = DefenderScanner(plan_id=1, is_component=False)
|
|
473
|
+
|
|
474
|
+
defender_asset = {
|
|
475
|
+
"resourceId": "/subscriptions/test/dns1",
|
|
476
|
+
"resourceName": "test.private.dns",
|
|
477
|
+
"resourceType": "microsoft.network/privatednszones",
|
|
478
|
+
"resourceLocation": "global",
|
|
479
|
+
"resourceGroup": "test-rg",
|
|
480
|
+
"ipAddress": "",
|
|
481
|
+
"properties": {},
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
asset = scanner.parse_asset(defender_asset)
|
|
485
|
+
|
|
486
|
+
assert asset.fqdn == "test.private.dns"
|
|
487
|
+
assert asset.software_function == "Dns zone that will connect to the private endpoint and network interfaces"
|