regscale-cli 6.27.2.0__py3-none-any.whl → 6.28.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/_version.py +1 -1
- regscale/core/app/application.py +1 -0
- regscale/core/app/internal/control_editor.py +73 -21
- regscale/core/app/internal/login.py +4 -1
- regscale/core/app/internal/model_editor.py +219 -64
- regscale/core/app/utils/app_utils.py +11 -2
- regscale/core/login.py +21 -4
- regscale/core/utils/date.py +77 -1
- regscale/dev/cli.py +26 -0
- regscale/dev/version.py +72 -0
- regscale/integrations/commercial/__init__.py +15 -1
- regscale/integrations/commercial/amazon/amazon/__init__.py +0 -0
- regscale/integrations/commercial/amazon/amazon/common.py +204 -0
- regscale/integrations/commercial/amazon/common.py +48 -58
- regscale/integrations/commercial/aws/audit_manager_compliance.py +2671 -0
- regscale/integrations/commercial/aws/cli.py +3093 -55
- regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
- regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
- regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
- regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
- regscale/integrations/commercial/aws/config_compliance.py +914 -0
- regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
- regscale/integrations/commercial/aws/evidence_generator.py +283 -0
- regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
- regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
- regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
- regscale/integrations/commercial/aws/iam_evidence.py +574 -0
- regscale/integrations/commercial/aws/inventory/__init__.py +223 -22
- regscale/integrations/commercial/aws/inventory/base.py +107 -5
- regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
- regscale/integrations/commercial/aws/inventory/resources/compute.py +66 -9
- regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
- regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
- regscale/integrations/commercial/aws/inventory/resources/database.py +106 -31
- regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
- regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
- regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
- regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
- regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
- regscale/integrations/commercial/aws/inventory/resources/networking.py +103 -67
- regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
- regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
- regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
- regscale/integrations/commercial/aws/inventory/resources/storage.py +53 -29
- regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
- regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
- regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
- regscale/integrations/commercial/aws/kms_evidence.py +879 -0
- regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
- regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
- regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
- regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
- regscale/integrations/commercial/aws/org_evidence.py +666 -0
- regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
- regscale/integrations/commercial/aws/s3_evidence.py +632 -0
- regscale/integrations/commercial/aws/scanner.py +853 -205
- regscale/integrations/commercial/aws/security_hub.py +319 -0
- regscale/integrations/commercial/aws/session_manager.py +282 -0
- regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
- regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
- regscale/integrations/commercial/synqly/query_builder.py +4 -1
- regscale/integrations/compliance_integration.py +308 -38
- regscale/integrations/control_matcher.py +78 -23
- regscale/integrations/due_date_handler.py +3 -0
- regscale/integrations/public/csam/csam.py +572 -763
- regscale/integrations/public/csam/csam_agency_defined.py +179 -0
- regscale/integrations/public/csam/csam_common.py +154 -0
- regscale/integrations/public/csam/csam_controls.py +432 -0
- regscale/integrations/public/csam/csam_poam.py +124 -0
- regscale/integrations/public/fedramp/click.py +17 -4
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +271 -62
- regscale/integrations/public/fedramp/poam/scanner.py +74 -7
- regscale/integrations/scanner_integration.py +415 -85
- regscale/models/integration_models/cisa_kev_data.json +80 -20
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +44 -3
- regscale/models/integration_models/synqly_models/ocsf_mapper.py +41 -12
- regscale/models/platform.py +3 -0
- regscale/models/regscale_models/__init__.py +5 -0
- regscale/models/regscale_models/assessment.py +2 -1
- regscale/models/regscale_models/component.py +1 -1
- regscale/models/regscale_models/control_implementation.py +55 -24
- regscale/models/regscale_models/control_objective.py +74 -5
- regscale/models/regscale_models/file.py +2 -0
- regscale/models/regscale_models/issue.py +2 -5
- regscale/models/regscale_models/organization.py +3 -0
- regscale/models/regscale_models/regscale_model.py +17 -5
- regscale/models/regscale_models/security_plan.py +1 -0
- regscale/regscale.py +11 -1
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/RECORD +140 -57
- tests/regscale/core/test_login.py +171 -4
- tests/regscale/integrations/commercial/aws/__init__.py +0 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
- tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
- tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
- tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
- tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
- tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
- tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
- tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
- tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
- tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
- tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
- tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
- tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
- tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
- tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
- tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
- tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
- tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
- tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
- tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
- tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
- tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
- tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
- tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
- tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
- tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
- tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
- tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
- tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
- tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
- tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
- tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
- tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
- tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
- tests/regscale/integrations/commercial/test_aws.py +55 -56
- tests/regscale/integrations/test_control_matcher.py +24 -0
- tests/regscale/models/test_control_implementation.py +118 -3
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"""Unit tests for AWS Evidence Generator."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import unittest
|
|
5
|
+
from datetime import datetime, timedelta
|
|
6
|
+
from unittest.mock import MagicMock, call, patch
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from regscale.integrations.commercial.aws.evidence_generator import AWSEvidenceGenerator
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestAWSEvidenceGenerator(unittest.TestCase):
|
|
14
|
+
"""Test cases for AWSEvidenceGenerator."""
|
|
15
|
+
|
|
16
|
+
def setUp(self):
|
|
17
|
+
"""Set up test fixtures."""
|
|
18
|
+
self.mock_api = MagicMock()
|
|
19
|
+
self.ssp_id = 456
|
|
20
|
+
self.generator = AWSEvidenceGenerator(api=self.mock_api, ssp_id=self.ssp_id)
|
|
21
|
+
|
|
22
|
+
def test_init(self):
|
|
23
|
+
"""Test AWSEvidenceGenerator initialization."""
|
|
24
|
+
assert self.generator.api == self.mock_api
|
|
25
|
+
assert self.generator.ssp_id == self.ssp_id
|
|
26
|
+
|
|
27
|
+
def test_init_without_ssp_id(self):
|
|
28
|
+
"""Test AWSEvidenceGenerator initialization without SSP ID."""
|
|
29
|
+
generator = AWSEvidenceGenerator(api=self.mock_api)
|
|
30
|
+
assert generator.api == self.mock_api
|
|
31
|
+
assert generator.ssp_id is None
|
|
32
|
+
|
|
33
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.EvidenceMapping")
|
|
34
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.Evidence")
|
|
35
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.File")
|
|
36
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.datetime")
|
|
37
|
+
def test_create_evidence_from_scan_success(self, mock_datetime, mock_file, mock_evidence, mock_evidence_mapping):
|
|
38
|
+
"""Test successful evidence creation from scan."""
|
|
39
|
+
# Setup mocks
|
|
40
|
+
mock_now = datetime(2025, 10, 13, 12, 0, 0)
|
|
41
|
+
mock_datetime.now.return_value = mock_now
|
|
42
|
+
|
|
43
|
+
findings = [
|
|
44
|
+
{
|
|
45
|
+
"Severity": {"Label": "HIGH"},
|
|
46
|
+
"Title": "Test Finding 1",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"Severity": {"Label": "MEDIUM"},
|
|
50
|
+
"Title": "Test Finding 2",
|
|
51
|
+
},
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
mock_evidence_instance = MagicMock()
|
|
55
|
+
mock_evidence_instance.id = 12345
|
|
56
|
+
mock_evidence_instance.title = "SecurityHub Findings Scan - 2025-10-13"
|
|
57
|
+
mock_evidence.return_value = mock_evidence_instance
|
|
58
|
+
mock_evidence_instance.create.return_value = mock_evidence_instance
|
|
59
|
+
|
|
60
|
+
mock_file.upload_file_to_regscale.return_value = True
|
|
61
|
+
|
|
62
|
+
# Mock EvidenceMapping
|
|
63
|
+
mock_mapping_instance = MagicMock()
|
|
64
|
+
mock_evidence_mapping.return_value = mock_mapping_instance
|
|
65
|
+
|
|
66
|
+
# Execute
|
|
67
|
+
result = self.generator.create_evidence_from_scan(
|
|
68
|
+
service_name="SecurityHub",
|
|
69
|
+
findings=findings,
|
|
70
|
+
ocsf_data=None,
|
|
71
|
+
update_frequency=30,
|
|
72
|
+
control_ids=None,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Verify evidence created
|
|
76
|
+
assert result == mock_evidence_instance
|
|
77
|
+
mock_evidence_instance.create.assert_called_once()
|
|
78
|
+
|
|
79
|
+
# Verify file uploads called
|
|
80
|
+
assert mock_file.upload_file_to_regscale.call_count == 1
|
|
81
|
+
|
|
82
|
+
# Verify SSP linking called (since self.ssp_id = 456 in setUp)
|
|
83
|
+
mock_evidence_mapping.assert_called_once()
|
|
84
|
+
|
|
85
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.logger")
|
|
86
|
+
def test_create_evidence_from_scan_no_findings(self, mock_logger):
|
|
87
|
+
"""Test evidence creation with no findings."""
|
|
88
|
+
result = self.generator.create_evidence_from_scan(
|
|
89
|
+
service_name="SecurityHub",
|
|
90
|
+
findings=[],
|
|
91
|
+
ocsf_data=None,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
assert result is None
|
|
95
|
+
mock_logger.warning.assert_called_once()
|
|
96
|
+
|
|
97
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.Evidence")
|
|
98
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.datetime")
|
|
99
|
+
def test_create_evidence_from_scan_creation_failure(self, mock_datetime, mock_evidence):
|
|
100
|
+
"""Test evidence creation failure."""
|
|
101
|
+
mock_now = datetime(2025, 10, 13, 12, 0, 0)
|
|
102
|
+
mock_datetime.now.return_value = mock_now
|
|
103
|
+
|
|
104
|
+
findings = [{"Severity": {"Label": "HIGH"}}]
|
|
105
|
+
|
|
106
|
+
mock_evidence_instance = MagicMock()
|
|
107
|
+
mock_evidence_instance.create.return_value = None
|
|
108
|
+
mock_evidence.return_value = mock_evidence_instance
|
|
109
|
+
|
|
110
|
+
result = self.generator.create_evidence_from_scan(
|
|
111
|
+
service_name="SecurityHub",
|
|
112
|
+
findings=findings,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
assert result is None
|
|
116
|
+
|
|
117
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.Evidence")
|
|
118
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.datetime")
|
|
119
|
+
def test_create_evidence_from_scan_exception(self, mock_datetime, mock_evidence):
|
|
120
|
+
"""Test evidence creation with exception."""
|
|
121
|
+
mock_now = datetime(2025, 10, 13, 12, 0, 0)
|
|
122
|
+
mock_datetime.now.return_value = mock_now
|
|
123
|
+
|
|
124
|
+
findings = [{"Severity": {"Label": "HIGH"}}]
|
|
125
|
+
|
|
126
|
+
mock_evidence.side_effect = Exception("Test error")
|
|
127
|
+
|
|
128
|
+
result = self.generator.create_evidence_from_scan(
|
|
129
|
+
service_name="SecurityHub",
|
|
130
|
+
findings=findings,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
assert result is None
|
|
134
|
+
|
|
135
|
+
def test_count_severities_guardduty(self):
|
|
136
|
+
"""Test severity counting for GuardDuty findings."""
|
|
137
|
+
findings = [
|
|
138
|
+
{"Severity": 8.0}, # HIGH
|
|
139
|
+
{"Severity": 7.5}, # HIGH
|
|
140
|
+
{"Severity": 5.0}, # MEDIUM
|
|
141
|
+
{"Severity": 4.0}, # MEDIUM
|
|
142
|
+
{"Severity": 2.0}, # LOW
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
result = self.generator._count_severities(findings, "GuardDuty")
|
|
146
|
+
|
|
147
|
+
assert result["HIGH"] == 2
|
|
148
|
+
assert result["MEDIUM"] == 2
|
|
149
|
+
assert result["LOW"] == 1
|
|
150
|
+
assert result["CRITICAL"] == 0
|
|
151
|
+
|
|
152
|
+
def test_count_severities_securityhub(self):
|
|
153
|
+
"""Test severity counting for Security Hub findings."""
|
|
154
|
+
findings = [
|
|
155
|
+
{"Severity": {"Label": "CRITICAL"}},
|
|
156
|
+
{"Severity": {"Label": "HIGH"}},
|
|
157
|
+
{"Severity": {"Label": "MEDIUM"}},
|
|
158
|
+
{"Severity": {"Label": "LOW"}},
|
|
159
|
+
{"Severity": {"Label": "INFORMATIONAL"}},
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
result = self.generator._count_severities(findings, "SecurityHub")
|
|
163
|
+
|
|
164
|
+
assert result["CRITICAL"] == 1
|
|
165
|
+
assert result["HIGH"] == 1
|
|
166
|
+
assert result["MEDIUM"] == 1
|
|
167
|
+
assert result["LOW"] == 1
|
|
168
|
+
assert result["INFO"] == 0
|
|
169
|
+
|
|
170
|
+
def test_count_severities_cloudtrail(self):
|
|
171
|
+
"""Test severity counting for CloudTrail events."""
|
|
172
|
+
findings = [
|
|
173
|
+
{"EventName": "DescribeInstances"},
|
|
174
|
+
{"EventName": "CreateBucket"},
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
result = self.generator._count_severities(findings, "CloudTrail")
|
|
178
|
+
|
|
179
|
+
assert result["INFO"] == 2
|
|
180
|
+
assert result["HIGH"] == 0
|
|
181
|
+
|
|
182
|
+
def test_build_evidence_description(self):
|
|
183
|
+
"""Test building evidence description."""
|
|
184
|
+
severity_counts = {
|
|
185
|
+
"CRITICAL": 2,
|
|
186
|
+
"HIGH": 5,
|
|
187
|
+
"MEDIUM": 10,
|
|
188
|
+
"LOW": 3,
|
|
189
|
+
"INFO": 0,
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
result = self.generator._build_evidence_description(
|
|
193
|
+
service_name="SecurityHub",
|
|
194
|
+
total_findings=20,
|
|
195
|
+
severity_counts=severity_counts,
|
|
196
|
+
ocsf_data=None,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
assert "SecurityHub" in result
|
|
200
|
+
assert "Total findings: 20" in result
|
|
201
|
+
assert "CRITICAL: 2" in result
|
|
202
|
+
assert "HIGH: 5" in result
|
|
203
|
+
assert "MEDIUM: 10" in result
|
|
204
|
+
assert "LOW: 3" in result
|
|
205
|
+
assert "INFO: 0" not in result # Should not show zero counts
|
|
206
|
+
assert "securityhub_findings_native.jsonl" in result
|
|
207
|
+
assert "securityhub_findings_ocsf.jsonl" not in result
|
|
208
|
+
|
|
209
|
+
def test_build_evidence_description_with_ocsf(self):
|
|
210
|
+
"""Test building evidence description with OCSF data."""
|
|
211
|
+
severity_counts = {"HIGH": 5}
|
|
212
|
+
ocsf_data = [{"class_uid": 2004}]
|
|
213
|
+
|
|
214
|
+
result = self.generator._build_evidence_description(
|
|
215
|
+
service_name="SecurityHub",
|
|
216
|
+
total_findings=5,
|
|
217
|
+
severity_counts=severity_counts,
|
|
218
|
+
ocsf_data=ocsf_data,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
assert "securityhub_findings_ocsf.jsonl" in result
|
|
222
|
+
|
|
223
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.File")
|
|
224
|
+
def test_attach_findings_files_native_only(self, mock_file):
|
|
225
|
+
"""Test attaching findings files (native only)."""
|
|
226
|
+
findings = [
|
|
227
|
+
{"Id": "finding-1", "Title": "Test 1"},
|
|
228
|
+
{"Id": "finding-2", "Title": "Test 2"},
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
mock_file.upload_file_to_regscale.return_value = True
|
|
232
|
+
|
|
233
|
+
self.generator._attach_findings_files(
|
|
234
|
+
evidence_id=123,
|
|
235
|
+
findings=findings,
|
|
236
|
+
ocsf_data=None,
|
|
237
|
+
service_name="SecurityHub",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Verify native file uploaded
|
|
241
|
+
assert mock_file.upload_file_to_regscale.call_count == 1
|
|
242
|
+
call_args = mock_file.upload_file_to_regscale.call_args
|
|
243
|
+
assert call_args[1]["parent_id"] == 123
|
|
244
|
+
assert call_args[1]["parent_module"] == "evidence"
|
|
245
|
+
assert "securityhub_findings_native.jsonl" in call_args[1]["file_name"]
|
|
246
|
+
assert "aws,securityhub,native" in call_args[1]["tags"]
|
|
247
|
+
|
|
248
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.File")
|
|
249
|
+
def test_attach_findings_files_with_ocsf(self, mock_file):
|
|
250
|
+
"""Test attaching findings files with OCSF."""
|
|
251
|
+
findings = [{"Id": "finding-1"}]
|
|
252
|
+
ocsf_data = [{"class_uid": 2004}]
|
|
253
|
+
|
|
254
|
+
mock_file.upload_file_to_regscale.return_value = True
|
|
255
|
+
|
|
256
|
+
self.generator._attach_findings_files(
|
|
257
|
+
evidence_id=123,
|
|
258
|
+
findings=findings,
|
|
259
|
+
ocsf_data=ocsf_data,
|
|
260
|
+
service_name="SecurityHub",
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Verify both files uploaded
|
|
264
|
+
assert mock_file.upload_file_to_regscale.call_count == 2
|
|
265
|
+
|
|
266
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.File")
|
|
267
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.logger")
|
|
268
|
+
def test_attach_findings_files_upload_failure(self, mock_logger, mock_file):
|
|
269
|
+
"""Test file attachment with upload failure."""
|
|
270
|
+
findings = [{"Id": "finding-1"}]
|
|
271
|
+
mock_file.upload_file_to_regscale.return_value = False
|
|
272
|
+
|
|
273
|
+
self.generator._attach_findings_files(
|
|
274
|
+
evidence_id=123,
|
|
275
|
+
findings=findings,
|
|
276
|
+
ocsf_data=None,
|
|
277
|
+
service_name="SecurityHub",
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Verify warning logged
|
|
281
|
+
assert mock_logger.warning.called
|
|
282
|
+
|
|
283
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.EvidenceMapping")
|
|
284
|
+
def test_link_to_ssp_success(self, mock_mapping):
|
|
285
|
+
"""Test linking evidence to SSP."""
|
|
286
|
+
mock_mapping_instance = MagicMock()
|
|
287
|
+
mock_mapping.return_value = mock_mapping_instance
|
|
288
|
+
|
|
289
|
+
self.generator._link_to_ssp(evidence_id=123)
|
|
290
|
+
|
|
291
|
+
# Verify mapping created
|
|
292
|
+
mock_mapping.assert_called_once_with(
|
|
293
|
+
evidenceID=123,
|
|
294
|
+
mappedID=self.ssp_id,
|
|
295
|
+
mappingType="securityplans",
|
|
296
|
+
)
|
|
297
|
+
mock_mapping_instance.create.assert_called_once()
|
|
298
|
+
|
|
299
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.EvidenceMapping")
|
|
300
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.logger")
|
|
301
|
+
def test_link_to_ssp_failure(self, mock_logger, mock_mapping):
|
|
302
|
+
"""Test linking evidence to SSP with failure."""
|
|
303
|
+
mock_mapping_instance = MagicMock()
|
|
304
|
+
mock_mapping_instance.create.side_effect = Exception("Test error")
|
|
305
|
+
mock_mapping.return_value = mock_mapping_instance
|
|
306
|
+
|
|
307
|
+
self.generator._link_to_ssp(evidence_id=123)
|
|
308
|
+
|
|
309
|
+
# Verify warning logged
|
|
310
|
+
mock_logger.warning.assert_called_once()
|
|
311
|
+
|
|
312
|
+
def test_link_to_ssp_no_ssp_id(self):
|
|
313
|
+
"""Test linking to SSP when no SSP ID configured."""
|
|
314
|
+
generator = AWSEvidenceGenerator(api=self.mock_api)
|
|
315
|
+
# Should not raise exception
|
|
316
|
+
generator._link_to_ssp(evidence_id=123)
|
|
317
|
+
|
|
318
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.EvidenceMapping")
|
|
319
|
+
def test_link_to_controls_success(self, mock_mapping):
|
|
320
|
+
"""Test linking evidence to controls."""
|
|
321
|
+
control_ids = [789, 790, 791]
|
|
322
|
+
mock_mapping_instance = MagicMock()
|
|
323
|
+
mock_mapping.return_value = mock_mapping_instance
|
|
324
|
+
|
|
325
|
+
self.generator._link_to_controls(evidence_id=123, control_ids=control_ids)
|
|
326
|
+
|
|
327
|
+
# Verify all mappings created
|
|
328
|
+
assert mock_mapping.call_count == 3
|
|
329
|
+
assert mock_mapping_instance.create.call_count == 3
|
|
330
|
+
|
|
331
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.EvidenceMapping")
|
|
332
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.logger")
|
|
333
|
+
def test_link_to_controls_partial_failure(self, mock_logger, mock_mapping):
|
|
334
|
+
"""Test linking to controls with partial failure."""
|
|
335
|
+
control_ids = [789, 790]
|
|
336
|
+
mock_mapping_instance = MagicMock()
|
|
337
|
+
mock_mapping_instance.create.side_effect = [None, Exception("Test error")]
|
|
338
|
+
mock_mapping.return_value = mock_mapping_instance
|
|
339
|
+
|
|
340
|
+
self.generator._link_to_controls(evidence_id=123, control_ids=control_ids)
|
|
341
|
+
|
|
342
|
+
# Verify warning logged for failure
|
|
343
|
+
mock_logger.warning.assert_called_once()
|
|
344
|
+
assert mock_mapping.call_count == 2
|
|
345
|
+
|
|
346
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.EvidenceMapping")
|
|
347
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.Evidence")
|
|
348
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.File")
|
|
349
|
+
@patch("regscale.integrations.commercial.aws.evidence_generator.datetime")
|
|
350
|
+
def test_create_evidence_with_all_options(self, mock_datetime, mock_file, mock_evidence, mock_evidence_mapping):
|
|
351
|
+
"""Test evidence creation with all options enabled."""
|
|
352
|
+
mock_now = datetime(2025, 10, 13, 12, 0, 0)
|
|
353
|
+
mock_datetime.now.return_value = mock_now
|
|
354
|
+
|
|
355
|
+
findings = [{"Severity": {"Label": "HIGH"}}]
|
|
356
|
+
ocsf_data = [{"class_uid": 2004}]
|
|
357
|
+
control_ids = [789, 790]
|
|
358
|
+
|
|
359
|
+
mock_evidence_instance = MagicMock()
|
|
360
|
+
mock_evidence_instance.id = 12345
|
|
361
|
+
mock_evidence.return_value = mock_evidence_instance
|
|
362
|
+
mock_evidence_instance.create.return_value = mock_evidence_instance
|
|
363
|
+
|
|
364
|
+
mock_file.upload_file_to_regscale.return_value = True
|
|
365
|
+
|
|
366
|
+
# Mock EvidenceMapping
|
|
367
|
+
mock_mapping_instance = MagicMock()
|
|
368
|
+
mock_evidence_mapping.return_value = mock_mapping_instance
|
|
369
|
+
|
|
370
|
+
result = self.generator.create_evidence_from_scan(
|
|
371
|
+
service_name="SecurityHub",
|
|
372
|
+
findings=findings,
|
|
373
|
+
ocsf_data=ocsf_data,
|
|
374
|
+
update_frequency=90,
|
|
375
|
+
control_ids=control_ids,
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
assert result is not None
|
|
379
|
+
# Verify both file uploads called
|
|
380
|
+
assert mock_file.upload_file_to_regscale.call_count == 2
|
|
381
|
+
# Verify SSP and control mappings called
|
|
382
|
+
assert mock_evidence_mapping.call_count == 3 # 1 SSP + 2 controls
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
if __name__ == "__main__":
|
|
386
|
+
pytest.main([__file__, "-v"])
|