regscale-cli 6.17.0.0__py3-none-any.whl → 6.19.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/__init__.py +1 -1
- regscale/core/app/api.py +5 -0
- regscale/core/login.py +3 -0
- regscale/integrations/api_paginator.py +932 -0
- regscale/integrations/api_paginator_example.py +348 -0
- regscale/integrations/commercial/__init__.py +11 -10
- regscale/integrations/commercial/burp.py +4 -0
- regscale/integrations/commercial/{qualys.py → qualys/__init__.py} +756 -105
- regscale/integrations/commercial/qualys/scanner.py +1051 -0
- regscale/integrations/commercial/qualys/variables.py +21 -0
- regscale/integrations/commercial/sicura/api.py +1 -0
- regscale/integrations/commercial/stigv2/click_commands.py +36 -8
- regscale/integrations/commercial/stigv2/stig_integration.py +63 -9
- regscale/integrations/commercial/tenablev2/__init__.py +9 -0
- regscale/integrations/commercial/tenablev2/authenticate.py +23 -2
- regscale/integrations/commercial/tenablev2/commands.py +779 -0
- regscale/integrations/commercial/tenablev2/jsonl_scanner.py +1999 -0
- regscale/integrations/commercial/tenablev2/sc_scanner.py +600 -0
- regscale/integrations/commercial/tenablev2/scanner.py +7 -5
- regscale/integrations/commercial/tenablev2/utils.py +21 -4
- regscale/integrations/commercial/tenablev2/variables.py +4 -0
- regscale/integrations/jsonl_scanner_integration.py +523 -142
- regscale/integrations/scanner_integration.py +102 -26
- regscale/integrations/transformer/__init__.py +17 -0
- regscale/integrations/transformer/data_transformer.py +445 -0
- regscale/integrations/transformer/mappings/__init__.py +8 -0
- regscale/integrations/variables.py +2 -0
- regscale/models/__init__.py +5 -2
- regscale/models/integration_models/cisa_kev_data.json +63 -7
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/regscale_models/asset.py +5 -2
- regscale/models/regscale_models/file.py +5 -2
- regscale/regscale.py +3 -1
- {regscale_cli-6.17.0.0.dist-info → regscale_cli-6.19.0.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.17.0.0.dist-info → regscale_cli-6.19.0.0.dist-info}/RECORD +47 -31
- tests/regscale/core/test_version.py +22 -0
- tests/regscale/integrations/__init__.py +0 -0
- tests/regscale/integrations/test_api_paginator.py +597 -0
- tests/regscale/integrations/test_integration_mapping.py +60 -0
- tests/regscale/integrations/test_issue_creation.py +317 -0
- tests/regscale/integrations/test_issue_due_date.py +46 -0
- tests/regscale/integrations/transformer/__init__.py +0 -0
- tests/regscale/integrations/transformer/test_data_transformer.py +850 -0
- regscale/integrations/commercial/tenablev2/click.py +0 -1637
- {regscale_cli-6.17.0.0.dist-info → regscale_cli-6.19.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.17.0.0.dist-info → regscale_cli-6.19.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.17.0.0.dist-info → regscale_cli-6.19.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.17.0.0.dist-info → regscale_cli-6.19.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from typing import List, Iterator
|
|
3
|
+
from unittest.mock import MagicMock
|
|
4
|
+
|
|
5
|
+
import freezegun
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from regscale.core.utils.date import date_str, get_day_increment
|
|
9
|
+
from regscale.integrations.scanner_integration import (
|
|
10
|
+
ScannerIntegration,
|
|
11
|
+
ScannerIntegrationType,
|
|
12
|
+
IntegrationAsset,
|
|
13
|
+
IntegrationFinding,
|
|
14
|
+
)
|
|
15
|
+
from regscale.integrations.variables import ScannerVariables
|
|
16
|
+
from regscale.models import regscale_models
|
|
17
|
+
from regscale.models.regscale_models.regscale_model import RegScaleModel
|
|
18
|
+
|
|
19
|
+
TEST_TIME = "2024-08-06 14:17:06"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def create_test_assets():
|
|
23
|
+
"""
|
|
24
|
+
Create test assets.
|
|
25
|
+
"""
|
|
26
|
+
for i in range(1, 3):
|
|
27
|
+
regscale_models.Asset(
|
|
28
|
+
id=i,
|
|
29
|
+
name=f"New Asset {i}",
|
|
30
|
+
otherTrackingNumber=f"new_asset{i}",
|
|
31
|
+
assetType="Server",
|
|
32
|
+
assetCategory="Physical",
|
|
33
|
+
assetOwnerId="test_owner",
|
|
34
|
+
status="Active (On Network)",
|
|
35
|
+
parentModule="securityplans",
|
|
36
|
+
parentId=1,
|
|
37
|
+
).create()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TestScannerIntegration(ScannerIntegration):
|
|
41
|
+
"""
|
|
42
|
+
A concrete implementation of ScannerIntegration for testing purposes.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
title = "Test Scanner"
|
|
46
|
+
asset_identifier_field = "otherTrackingNumber"
|
|
47
|
+
type = ScannerIntegrationType.VULNERABILITY
|
|
48
|
+
|
|
49
|
+
def fetch_assets(self, *args, **kwargs) -> Iterator[IntegrationAsset]:
|
|
50
|
+
"""
|
|
51
|
+
Fetch mock assets for testing.
|
|
52
|
+
|
|
53
|
+
:param args: Variable length argument list
|
|
54
|
+
:param kwargs: Arbitrary keyword arguments
|
|
55
|
+
:return: An iterator of IntegrationAsset objects
|
|
56
|
+
"""
|
|
57
|
+
assets = [
|
|
58
|
+
IntegrationAsset(
|
|
59
|
+
identifier=f"new_asset{i}",
|
|
60
|
+
name=f"New Asset {i}",
|
|
61
|
+
asset_type="Server",
|
|
62
|
+
asset_category="Physical",
|
|
63
|
+
software_inventory=[],
|
|
64
|
+
ports_and_protocols=[],
|
|
65
|
+
source_data={"key": "value"},
|
|
66
|
+
asset_owner_id="test_owner",
|
|
67
|
+
url="http://example.com",
|
|
68
|
+
)
|
|
69
|
+
for i in range(1, 3)
|
|
70
|
+
]
|
|
71
|
+
return iter(assets)
|
|
72
|
+
|
|
73
|
+
def fetch_findings(self, *args, **kwargs) -> List[IntegrationFinding]:
|
|
74
|
+
"""
|
|
75
|
+
Fetch mock findings for testing.
|
|
76
|
+
|
|
77
|
+
:rtype: List[IntegrationFinding]
|
|
78
|
+
:return: A list of IntegrationFinding objects
|
|
79
|
+
"""
|
|
80
|
+
return [
|
|
81
|
+
IntegrationFinding(
|
|
82
|
+
title=f"Test Finding {i}",
|
|
83
|
+
asset_identifier=f"new_asset{i}",
|
|
84
|
+
description="This is a test finding description",
|
|
85
|
+
external_id=f"FINDING-00{i}",
|
|
86
|
+
remediation="Apply the latest security patch",
|
|
87
|
+
control_labels=[],
|
|
88
|
+
category="Vulnerability",
|
|
89
|
+
severity=regscale_models.IssueSeverity.High,
|
|
90
|
+
status=regscale_models.IssueStatus.Open,
|
|
91
|
+
cve="CVE-2023-12345",
|
|
92
|
+
first_seen=TEST_TIME,
|
|
93
|
+
last_seen=TEST_TIME,
|
|
94
|
+
plugin_name="Testing Issue Creation",
|
|
95
|
+
)
|
|
96
|
+
for i in range(1, 3)
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def mark_asset_inactive(asset: regscale_models.Asset) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Mark an asset as inactive.
|
|
103
|
+
|
|
104
|
+
:param regscale_models.Asset asset: The asset to mark as inactive
|
|
105
|
+
"""
|
|
106
|
+
asset.status = "Inactive"
|
|
107
|
+
asset.save()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@pytest.fixture(autouse=True)
|
|
111
|
+
def setup_scanner(mock_regscale_models) -> TestScannerIntegration:
|
|
112
|
+
"""
|
|
113
|
+
Fixture to set up a TestScannerIntegration instance for testing.
|
|
114
|
+
|
|
115
|
+
:param MagicMock mock_regscale_models: Mocked RegScaleModel
|
|
116
|
+
:rtype: TestScannerIntegration
|
|
117
|
+
:return: A configured TestScannerIntegration instance
|
|
118
|
+
"""
|
|
119
|
+
RegScaleModel.clear_cache()
|
|
120
|
+
create_test_assets()
|
|
121
|
+
scanner = TestScannerIntegration(plan_id=1)
|
|
122
|
+
scanner.regscale_version = "5.64.0"
|
|
123
|
+
return scanner
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class TestVulnerabilityScanning:
|
|
127
|
+
"""
|
|
128
|
+
Test class for vulnerability scanning functionality.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
def test_sync_assets(self, mock_regscale_models, setup_scanner):
|
|
132
|
+
"""
|
|
133
|
+
Test the sync_assets method of the scanner.
|
|
134
|
+
|
|
135
|
+
:param MagicMock mock_regscale_models: Mocked RegScaleModel
|
|
136
|
+
:param TestScannerIntegration setup_scanner: Configured TestScannerIntegration instance
|
|
137
|
+
"""
|
|
138
|
+
# COVERED: The sync_assets method calls instance.update_regscale_assets(assets=assets),
|
|
139
|
+
# which handles asset creation for non-existent assets.
|
|
140
|
+
scanner = setup_scanner
|
|
141
|
+
|
|
142
|
+
# Call the sync_assets method
|
|
143
|
+
processed_assets_count = scanner.sync_assets(plan_id=1)
|
|
144
|
+
|
|
145
|
+
# Verify that two assets were processed
|
|
146
|
+
assert processed_assets_count == 2
|
|
147
|
+
|
|
148
|
+
assets: List[regscale_models.Asset] = regscale_models.Asset.get_all_by_parent(
|
|
149
|
+
parent_id=1, parent_module=regscale_models.SecurityPlan.get_module_string()
|
|
150
|
+
)
|
|
151
|
+
assert len(assets) == 2
|
|
152
|
+
created_asset = assets[0]
|
|
153
|
+
|
|
154
|
+
# Check that the asset was created with the correct values
|
|
155
|
+
assert created_asset == regscale_models.Asset(
|
|
156
|
+
id=created_asset.id,
|
|
157
|
+
name="New Asset 1",
|
|
158
|
+
otherTrackingNumber="new_asset1",
|
|
159
|
+
assetType="Server",
|
|
160
|
+
assetCategory="Physical",
|
|
161
|
+
assetOwnerId="test_owner",
|
|
162
|
+
status="Active (On Network)",
|
|
163
|
+
parentModule="securityplans",
|
|
164
|
+
parentId=created_asset.parentId,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
@freezegun.freeze_time(TEST_TIME)
|
|
168
|
+
@pytest.mark.parametrize(
|
|
169
|
+
"issue_creation,vulnerability_creation,expected_issue_count,expected_poam,expected_second_asset_issues",
|
|
170
|
+
[
|
|
171
|
+
("PerAsset", "IssueCreation", 2, False, 1),
|
|
172
|
+
("Consolidated", "PoamCreation", 1, True, 0),
|
|
173
|
+
],
|
|
174
|
+
)
|
|
175
|
+
def test_sync_findings(
|
|
176
|
+
self,
|
|
177
|
+
mock_regscale_models,
|
|
178
|
+
setup_scanner,
|
|
179
|
+
issue_creation,
|
|
180
|
+
vulnerability_creation,
|
|
181
|
+
expected_issue_count,
|
|
182
|
+
expected_poam,
|
|
183
|
+
expected_second_asset_issues,
|
|
184
|
+
):
|
|
185
|
+
"""
|
|
186
|
+
Test the sync_findings method with different configurations.
|
|
187
|
+
|
|
188
|
+
:param MagicMock mock_regscale_models: Mocked RegScaleModel
|
|
189
|
+
:param TestScannerIntegration setup_scanner: Configured TestScannerIntegration instance
|
|
190
|
+
:param str issue_creation: The issue creation strategy
|
|
191
|
+
:param str vulnerability_creation: The vulnerability creation strategy
|
|
192
|
+
:param int expected_issue_count: The expected number of issues created
|
|
193
|
+
:param bool expected_poam: Whether the created issue should be a POAM
|
|
194
|
+
:param int expected_second_asset_issues: The expected number of issues for the second asset
|
|
195
|
+
"""
|
|
196
|
+
# COVERED: This test checks the creation of a scan history per import,
|
|
197
|
+
# creation of vulnerabilities for each finding as a child of the affected asset,
|
|
198
|
+
# and creation of issues per unique asset / vulnerability pair.
|
|
199
|
+
ScannerVariables.issueCreation = issue_creation
|
|
200
|
+
ScannerVariables.vulnerabilityCreation = vulnerability_creation
|
|
201
|
+
scanner = setup_scanner
|
|
202
|
+
|
|
203
|
+
# Call the sync_findings method
|
|
204
|
+
processed_findings_count = scanner.sync_findings(plan_id=1)
|
|
205
|
+
|
|
206
|
+
# Verify that two findings were processed
|
|
207
|
+
assert processed_findings_count == 2
|
|
208
|
+
|
|
209
|
+
# Check that a scan history was created
|
|
210
|
+
scan_histories: List[regscale_models.ScanHistory] = regscale_models.ScanHistory.get_all_by_parent(
|
|
211
|
+
parent_id=1, parent_module=regscale_models.SecurityPlan.get_module_string()
|
|
212
|
+
)
|
|
213
|
+
assert len(scan_histories) == 1
|
|
214
|
+
created_scan_history = scan_histories[0]
|
|
215
|
+
|
|
216
|
+
assert created_scan_history == regscale_models.ScanHistory(
|
|
217
|
+
id=created_scan_history.id,
|
|
218
|
+
scanDate=created_scan_history.scanDate,
|
|
219
|
+
scanningTool="Test Scanner",
|
|
220
|
+
parentModule="securityplans",
|
|
221
|
+
parentId=1,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Check that a vulnerability was created
|
|
225
|
+
vulnerabilities: List[regscale_models.Vulnerability] = regscale_models.Vulnerability.get_all_by_parent(
|
|
226
|
+
parent_id=1, parent_module=regscale_models.SecurityPlan.get_module_string()
|
|
227
|
+
)
|
|
228
|
+
assert len(vulnerabilities) == 1
|
|
229
|
+
for vulnerability in vulnerabilities:
|
|
230
|
+
if vulnerability.title == "Test Finding 2":
|
|
231
|
+
created_vulnerability = vulnerability
|
|
232
|
+
break
|
|
233
|
+
|
|
234
|
+
assert created_vulnerability == regscale_models.Vulnerability( # type: ignore
|
|
235
|
+
id=created_vulnerability.id,
|
|
236
|
+
title="Test Finding 2",
|
|
237
|
+
description="This is a test finding description",
|
|
238
|
+
severity=regscale_models.VulnerabilitySeverity.High,
|
|
239
|
+
status=regscale_models.VulnerabilityStatus.Open,
|
|
240
|
+
parent_id=1,
|
|
241
|
+
parent_module=regscale_models.SecurityPlan.get_module_string(),
|
|
242
|
+
dns="unknown",
|
|
243
|
+
cve="CVE-2023-12345",
|
|
244
|
+
plugInName="CVE-2023-12345",
|
|
245
|
+
plugInText="",
|
|
246
|
+
scan_id=created_scan_history.id,
|
|
247
|
+
last_seen=TEST_TIME,
|
|
248
|
+
first_seen=TEST_TIME,
|
|
249
|
+
plug_in_name="CVE-2023-12345",
|
|
250
|
+
plug_in_text="",
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
# Check that issues were created as expected
|
|
254
|
+
total_issues = 0
|
|
255
|
+
assets: List[regscale_models.Asset] = regscale_models.Asset.get_all_by_parent(
|
|
256
|
+
parent_id=1, parent_module=regscale_models.SecurityPlan.get_module_string()
|
|
257
|
+
)
|
|
258
|
+
for asset in assets:
|
|
259
|
+
issues: List[regscale_models.Issue] = regscale_models.Issue.get_all_by_parent(
|
|
260
|
+
parent_id=asset.id, parent_module="assets"
|
|
261
|
+
)
|
|
262
|
+
total_issues += len(issues)
|
|
263
|
+
|
|
264
|
+
assert total_issues == expected_issue_count
|
|
265
|
+
|
|
266
|
+
# Get the first created issue for further checks
|
|
267
|
+
first_asset_issues: List[regscale_models.Issue] = (
|
|
268
|
+
regscale_models.Issue.get_all_by_parent(parent_id=assets[0].id, parent_module="assets") if assets else []
|
|
269
|
+
)
|
|
270
|
+
created_issue = first_asset_issues[0] if first_asset_issues else None
|
|
271
|
+
|
|
272
|
+
expected_asset_identifier = "new_asset1\nnew_asset2" if issue_creation == "Consolidated" else "new_asset1"
|
|
273
|
+
expected_other_identifier = (
|
|
274
|
+
"1:CVE-2023-12345" if issue_creation == "Consolidated" else "1:CVE-2023-12345:new_asset1"
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
if created_issue:
|
|
278
|
+
assert created_issue == regscale_models.Issue(
|
|
279
|
+
id=created_issue.id,
|
|
280
|
+
title="Test Finding 1",
|
|
281
|
+
severityLevel=regscale_models.IssueSeverity.High,
|
|
282
|
+
issueOwnerId=scanner.get_assessor_id(),
|
|
283
|
+
dueDate=date_str(get_day_increment(TEST_TIME, 60)),
|
|
284
|
+
identification="Vulnerability Assessment",
|
|
285
|
+
sourceReport="Test Scanner",
|
|
286
|
+
description="This is a test finding description",
|
|
287
|
+
status="Open",
|
|
288
|
+
securityPlanId=1,
|
|
289
|
+
cve="CVE-2023-12345",
|
|
290
|
+
assetIdentifier=expected_asset_identifier,
|
|
291
|
+
dateFirstDetected=TEST_TIME,
|
|
292
|
+
parentId=assets[0].id,
|
|
293
|
+
parentModule="assets",
|
|
294
|
+
dateLastUpdated=TEST_TIME,
|
|
295
|
+
securityChecks="FINDING-001",
|
|
296
|
+
recommendedActions="",
|
|
297
|
+
isPoam=expected_poam,
|
|
298
|
+
otherIdentifier=expected_other_identifier,
|
|
299
|
+
remediationDescription="",
|
|
300
|
+
vulnerabilityId=created_vulnerability.id,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Check issues for the second asset
|
|
304
|
+
second_asset_issues: List[regscale_models.Issue] = regscale_models.Issue.get_all_by_parent(
|
|
305
|
+
parent_id=assets[1].id, parent_module="assets"
|
|
306
|
+
)
|
|
307
|
+
assert len(second_asset_issues) == expected_second_asset_issues
|
|
308
|
+
|
|
309
|
+
# NOTE: To fully confirm coverage of all test cases, additional tests may be needed to check:
|
|
310
|
+
# - Closing of vulnerabilities that no longer exist in the scan
|
|
311
|
+
# - Updating of existing issues when findings change
|
|
312
|
+
# - Closing of issues that are no longer in the scan
|
|
313
|
+
# - Handling of different vulnerability creation and issue creation configurations
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
if __name__ == "__main__":
|
|
317
|
+
unittest.main()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from regscale.integrations.scanner_integration import issue_due_date
|
|
6
|
+
from regscale.models import regscale_models
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_issue_due_date():
|
|
10
|
+
# Test data
|
|
11
|
+
created_date = "2023-01-01"
|
|
12
|
+
severity = regscale_models.IssueSeverity.High
|
|
13
|
+
title = "test_integration"
|
|
14
|
+
config = {"issues": {"test_integration": {"high": 1, "moderate": 2, "low": 3}}}
|
|
15
|
+
|
|
16
|
+
# Expected due date
|
|
17
|
+
expected_due_date = "2023-01-02"
|
|
18
|
+
|
|
19
|
+
# Call the function
|
|
20
|
+
result = issue_due_date(severity, created_date, title=title, config=config)
|
|
21
|
+
|
|
22
|
+
# Assert the result
|
|
23
|
+
assert result == expected_due_date
|
|
24
|
+
|
|
25
|
+
# Test with low value
|
|
26
|
+
severity = regscale_models.IssueSeverity.Low
|
|
27
|
+
expected_due_date = "2023-01-04"
|
|
28
|
+
result = issue_due_date(severity, created_date, title=title, config=config)
|
|
29
|
+
assert result == expected_due_date
|
|
30
|
+
|
|
31
|
+
# Test with defaults
|
|
32
|
+
severity = regscale_models.IssueSeverity.Moderate
|
|
33
|
+
expected_due_date = "2023-07-30"
|
|
34
|
+
result = issue_due_date(severity, created_date)
|
|
35
|
+
assert result == expected_due_date
|
|
36
|
+
|
|
37
|
+
# Test with no matching title in config
|
|
38
|
+
severity = regscale_models.IssueSeverity.Low
|
|
39
|
+
title = "Nonexistent_Integration"
|
|
40
|
+
expected_due_date = "2023-12-31" # 364 days is the default
|
|
41
|
+
result = issue_due_date(severity=severity, created_date=created_date, title=title, config=config)
|
|
42
|
+
assert result == expected_due_date
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
pytest.main()
|
|
File without changes
|