regscale-cli 6.27.3.0__py3-none-any.whl → 6.28.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/_version.py +1 -1
- regscale/core/app/utils/app_utils.py +11 -2
- 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 +851 -206
- 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/ticketing.py +27 -0
- regscale/integrations/compliance_integration.py +308 -38
- regscale/integrations/due_date_handler.py +3 -0
- regscale/integrations/scanner_integration.py +399 -84
- regscale/models/integration_models/cisa_kev_data.json +65 -5
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +17 -9
- regscale/models/regscale_models/assessment.py +2 -1
- 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_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/RECORD +113 -34
- 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
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Unit tests for AWS Config Compliance Integration."""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import pytest
|
|
7
|
+
from unittest.mock import Mock, MagicMock, patch, mock_open
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
|
|
10
|
+
from regscale.integrations.commercial.aws.config_compliance import (
|
|
11
|
+
AWSConfigCompliance,
|
|
12
|
+
AWSConfigComplianceItem,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestAWSConfigComplianceItem:
|
|
17
|
+
"""Test AWS Config Compliance Item."""
|
|
18
|
+
|
|
19
|
+
def test_initialization(self):
|
|
20
|
+
"""Test compliance item initialization."""
|
|
21
|
+
rule_evaluations = [
|
|
22
|
+
{"rule_name": "test-rule-1", "compliance_type": "COMPLIANT", "non_compliant_resource_count": 0},
|
|
23
|
+
{"rule_name": "test-rule-2", "compliance_type": "NON_COMPLIANT", "non_compliant_resource_count": 5},
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
item = AWSConfigComplianceItem(
|
|
27
|
+
control_id="AC-2",
|
|
28
|
+
control_name="Account Management",
|
|
29
|
+
framework="NIST800-53R5",
|
|
30
|
+
rule_evaluations=rule_evaluations,
|
|
31
|
+
resource_id="123456789012",
|
|
32
|
+
resource_name="Test Account",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
assert item.control_id == "AC-2"
|
|
36
|
+
assert item._control_name == "Account Management"
|
|
37
|
+
assert item.framework == "NIST800-53R5"
|
|
38
|
+
assert len(item.rule_evaluations) == 2
|
|
39
|
+
assert item.resource_id == "123456789012"
|
|
40
|
+
assert item.resource_name == "Test Account"
|
|
41
|
+
|
|
42
|
+
def test_compliance_result_fail(self):
|
|
43
|
+
"""Test compliance result returns FAIL for non-compliant rules."""
|
|
44
|
+
rule_evaluations = [
|
|
45
|
+
{"rule_name": "test-rule-1", "compliance_type": "COMPLIANT", "non_compliant_resource_count": 0},
|
|
46
|
+
{"rule_name": "test-rule-2", "compliance_type": "NON_COMPLIANT", "non_compliant_resource_count": 5},
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
item = AWSConfigComplianceItem(
|
|
50
|
+
control_id="AC-2",
|
|
51
|
+
control_name="Account Management",
|
|
52
|
+
framework="NIST800-53R5",
|
|
53
|
+
rule_evaluations=rule_evaluations,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
assert item.compliance_result == "FAIL"
|
|
57
|
+
|
|
58
|
+
def test_compliance_result_pass(self):
|
|
59
|
+
"""Test compliance result returns PASS for all compliant rules."""
|
|
60
|
+
rule_evaluations = [
|
|
61
|
+
{"rule_name": "test-rule-1", "compliance_type": "COMPLIANT", "non_compliant_resource_count": 0},
|
|
62
|
+
{"rule_name": "test-rule-2", "compliance_type": "COMPLIANT", "non_compliant_resource_count": 0},
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
item = AWSConfigComplianceItem(
|
|
66
|
+
control_id="AC-2",
|
|
67
|
+
control_name="Account Management",
|
|
68
|
+
framework="NIST800-53R5",
|
|
69
|
+
rule_evaluations=rule_evaluations,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
assert item.compliance_result == "PASS"
|
|
73
|
+
|
|
74
|
+
def test_compliance_result_no_data(self):
|
|
75
|
+
"""Test compliance result returns None for no data."""
|
|
76
|
+
item = AWSConfigComplianceItem(
|
|
77
|
+
control_id="AC-2",
|
|
78
|
+
control_name="Account Management",
|
|
79
|
+
framework="NIST800-53R5",
|
|
80
|
+
rule_evaluations=[],
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
assert item.compliance_result is None
|
|
84
|
+
|
|
85
|
+
def test_severity_for_failed_control(self):
|
|
86
|
+
"""Test severity calculation for failed controls."""
|
|
87
|
+
# Test HIGH severity (>= 5 non-compliant rules)
|
|
88
|
+
rule_evaluations_high = [
|
|
89
|
+
{"rule_name": f"test-rule-{i}", "compliance_type": "NON_COMPLIANT", "non_compliant_resource_count": 1}
|
|
90
|
+
for i in range(6)
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
item_high = AWSConfigComplianceItem(
|
|
94
|
+
control_id="AC-2",
|
|
95
|
+
control_name="Account Management",
|
|
96
|
+
framework="NIST800-53R5",
|
|
97
|
+
rule_evaluations=rule_evaluations_high,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
assert item_high.severity == "HIGH"
|
|
101
|
+
|
|
102
|
+
# Test MEDIUM severity (2-4 non-compliant rules)
|
|
103
|
+
rule_evaluations_medium = [
|
|
104
|
+
{"rule_name": f"test-rule-{i}", "compliance_type": "NON_COMPLIANT", "non_compliant_resource_count": 1}
|
|
105
|
+
for i in range(3)
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
item_medium = AWSConfigComplianceItem(
|
|
109
|
+
control_id="AC-2",
|
|
110
|
+
control_name="Account Management",
|
|
111
|
+
framework="NIST800-53R5",
|
|
112
|
+
rule_evaluations=rule_evaluations_medium,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
assert item_medium.severity == "MEDIUM"
|
|
116
|
+
|
|
117
|
+
# Test LOW severity (1 non-compliant rule)
|
|
118
|
+
rule_evaluations_low = [
|
|
119
|
+
{"rule_name": "test-rule-1", "compliance_type": "NON_COMPLIANT", "non_compliant_resource_count": 1}
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
item_low = AWSConfigComplianceItem(
|
|
123
|
+
control_id="AC-2",
|
|
124
|
+
control_name="Account Management",
|
|
125
|
+
framework="NIST800-53R5",
|
|
126
|
+
rule_evaluations=rule_evaluations_low,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
assert item_low.severity == "LOW"
|
|
130
|
+
|
|
131
|
+
def test_severity_for_passed_control(self):
|
|
132
|
+
"""Test severity is None for passed controls."""
|
|
133
|
+
rule_evaluations = [
|
|
134
|
+
{"rule_name": "test-rule-1", "compliance_type": "COMPLIANT", "non_compliant_resource_count": 0}
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
item = AWSConfigComplianceItem(
|
|
138
|
+
control_id="AC-2",
|
|
139
|
+
control_name="Account Management",
|
|
140
|
+
framework="NIST800-53R5",
|
|
141
|
+
rule_evaluations=rule_evaluations,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
assert item.severity is None
|
|
145
|
+
|
|
146
|
+
def test_description_html_format(self):
|
|
147
|
+
"""Test description contains HTML formatting."""
|
|
148
|
+
rule_evaluations = [
|
|
149
|
+
{"rule_name": "test-rule-1", "compliance_type": "COMPLIANT", "non_compliant_resource_count": 0},
|
|
150
|
+
{"rule_name": "test-rule-2", "compliance_type": "NON_COMPLIANT", "non_compliant_resource_count": 5},
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
item = AWSConfigComplianceItem(
|
|
154
|
+
control_id="AC-2",
|
|
155
|
+
control_name="Account Management",
|
|
156
|
+
framework="NIST800-53R5",
|
|
157
|
+
rule_evaluations=rule_evaluations,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
description = item.description
|
|
161
|
+
|
|
162
|
+
assert "<h3>" in description
|
|
163
|
+
assert "AC-2" in description
|
|
164
|
+
assert "<strong>" in description
|
|
165
|
+
assert "Total Rules" in description
|
|
166
|
+
assert "Compliant Rules" in description
|
|
167
|
+
assert "Non-Compliant Rules" in description
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class TestAWSConfigCompliance:
|
|
171
|
+
"""Test AWS Config Compliance Integration."""
|
|
172
|
+
|
|
173
|
+
@pytest.fixture
|
|
174
|
+
def mock_boto_session(self):
|
|
175
|
+
"""Create a mock boto3 session."""
|
|
176
|
+
with patch("boto3.Session") as mock_session:
|
|
177
|
+
mock_session_instance = MagicMock()
|
|
178
|
+
mock_session.return_value = mock_session_instance
|
|
179
|
+
|
|
180
|
+
# Mock clients
|
|
181
|
+
mock_config_client = MagicMock()
|
|
182
|
+
mock_sts_client = MagicMock()
|
|
183
|
+
|
|
184
|
+
# Setup client creation
|
|
185
|
+
def get_client(service_name):
|
|
186
|
+
if service_name == "config":
|
|
187
|
+
return mock_config_client
|
|
188
|
+
elif service_name == "sts":
|
|
189
|
+
return mock_sts_client
|
|
190
|
+
return MagicMock()
|
|
191
|
+
|
|
192
|
+
mock_session_instance.client.side_effect = get_client
|
|
193
|
+
|
|
194
|
+
# Mock STS get_caller_identity
|
|
195
|
+
mock_sts_client.get_caller_identity.return_value = {"Account": "123456789012"}
|
|
196
|
+
|
|
197
|
+
yield {
|
|
198
|
+
"session": mock_session_instance,
|
|
199
|
+
"config_client": mock_config_client,
|
|
200
|
+
"sts_client": mock_sts_client,
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
def test_initialization(self, mock_boto_session):
|
|
204
|
+
"""Test AWS Config Compliance initialization."""
|
|
205
|
+
with patch("boto3.Session", return_value=mock_boto_session["session"]):
|
|
206
|
+
scanner = AWSConfigCompliance(
|
|
207
|
+
plan_id=123,
|
|
208
|
+
region="us-east-1",
|
|
209
|
+
framework="NIST800-53R5",
|
|
210
|
+
create_issues=True,
|
|
211
|
+
update_control_status=True,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
assert scanner.plan_id == 123
|
|
215
|
+
assert scanner.region == "us-east-1"
|
|
216
|
+
assert scanner.framework == "NIST800-53R5"
|
|
217
|
+
assert scanner.create_issues is True
|
|
218
|
+
assert scanner.update_control_status is True
|
|
219
|
+
assert scanner.title == "AWS Config"
|
|
220
|
+
|
|
221
|
+
def test_cache_validation(self, mock_boto_session):
|
|
222
|
+
"""Test cache validation logic."""
|
|
223
|
+
with patch("boto3.Session", return_value=mock_boto_session["session"]):
|
|
224
|
+
scanner = AWSConfigCompliance(plan_id=123, region="us-east-1")
|
|
225
|
+
|
|
226
|
+
# Test with no cache file
|
|
227
|
+
with patch("os.path.exists", return_value=False):
|
|
228
|
+
assert scanner._is_cache_valid() is False
|
|
229
|
+
|
|
230
|
+
# Test with valid cache file
|
|
231
|
+
with patch("os.path.exists", return_value=True):
|
|
232
|
+
with patch("os.path.getmtime", return_value=datetime.now().timestamp() - 3600): # 1 hour old
|
|
233
|
+
assert scanner._is_cache_valid() is True
|
|
234
|
+
|
|
235
|
+
# Test with expired cache file
|
|
236
|
+
with patch("os.path.exists", return_value=True):
|
|
237
|
+
with patch("os.path.getmtime", return_value=datetime.now().timestamp() - (5 * 3600)): # 5 hours old
|
|
238
|
+
assert scanner._is_cache_valid() is False
|
|
239
|
+
|
|
240
|
+
def test_create_compliance_item(self, mock_boto_session):
|
|
241
|
+
"""Test creating compliance item from raw data."""
|
|
242
|
+
with patch("boto3.Session", return_value=mock_boto_session["session"]):
|
|
243
|
+
scanner = AWSConfigCompliance(plan_id=123, region="us-east-1")
|
|
244
|
+
|
|
245
|
+
raw_data = {
|
|
246
|
+
"control_id": "AC-2",
|
|
247
|
+
"control_name": "Account Management",
|
|
248
|
+
"rule_evaluations": [
|
|
249
|
+
{"rule_name": "test-rule-1", "compliance_type": "COMPLIANT", "non_compliant_resource_count": 0}
|
|
250
|
+
],
|
|
251
|
+
"resource_id": "123456789012",
|
|
252
|
+
"resource_name": "Test Account",
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
item = scanner.create_compliance_item(raw_data)
|
|
256
|
+
|
|
257
|
+
assert isinstance(item, AWSConfigComplianceItem)
|
|
258
|
+
assert item.control_id == "AC-2"
|
|
259
|
+
assert len(item.rule_evaluations) == 1
|
|
260
|
+
|
|
261
|
+
def test_get_aws_account_id(self, mock_boto_session):
|
|
262
|
+
"""Test getting AWS account ID."""
|
|
263
|
+
with patch("boto3.Session", return_value=mock_boto_session["session"]):
|
|
264
|
+
scanner = AWSConfigCompliance(plan_id=123, region="us-east-1")
|
|
265
|
+
|
|
266
|
+
account_id = scanner._get_aws_account_id()
|
|
267
|
+
assert account_id == "123456789012"
|
|
268
|
+
|
|
269
|
+
def test_load_cached_data(self, mock_boto_session):
|
|
270
|
+
"""Test loading data from cache."""
|
|
271
|
+
with patch("boto3.Session", return_value=mock_boto_session["session"]):
|
|
272
|
+
scanner = AWSConfigCompliance(plan_id=123, region="us-east-1")
|
|
273
|
+
|
|
274
|
+
cached_data = [{"control_id": "AC-2", "rule_evaluations": []}]
|
|
275
|
+
|
|
276
|
+
mock_file_content = json.dumps(cached_data)
|
|
277
|
+
|
|
278
|
+
with patch("builtins.open", mock_open(read_data=mock_file_content)):
|
|
279
|
+
loaded_data = scanner._load_cached_data()
|
|
280
|
+
assert len(loaded_data) == 1
|
|
281
|
+
assert loaded_data[0]["control_id"] == "AC-2"
|
|
282
|
+
|
|
283
|
+
def test_save_to_cache(self, mock_boto_session):
|
|
284
|
+
"""Test saving data to cache."""
|
|
285
|
+
with patch("boto3.Session", return_value=mock_boto_session["session"]):
|
|
286
|
+
scanner = AWSConfigCompliance(plan_id=123, region="us-east-1")
|
|
287
|
+
|
|
288
|
+
compliance_data = [{"control_id": "AC-2", "rule_evaluations": []}]
|
|
289
|
+
|
|
290
|
+
with patch("os.makedirs") as mock_makedirs:
|
|
291
|
+
with patch("builtins.open", mock_open()) as mock_file:
|
|
292
|
+
scanner._save_to_cache(compliance_data)
|
|
293
|
+
mock_makedirs.assert_called_once()
|
|
294
|
+
mock_file.assert_called_once()
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
if __name__ == "__main__":
|
|
298
|
+
pytest.main([__file__, "-v"])
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Unit tests for AWS Config Conformance Pack Mappings."""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from regscale.integrations.commercial.aws.conformance_pack_mappings import (
|
|
8
|
+
extract_control_ids_from_rule_name,
|
|
9
|
+
extract_control_ids_from_tags,
|
|
10
|
+
get_control_mappings_for_framework,
|
|
11
|
+
map_rule_to_controls,
|
|
12
|
+
NIST_80053_R5_MAPPINGS,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestExtractControlIdsFromRuleName:
|
|
17
|
+
"""Test extracting control IDs from rule names."""
|
|
18
|
+
|
|
19
|
+
def test_extract_single_control(self):
|
|
20
|
+
"""Test extracting a single control ID."""
|
|
21
|
+
rule_name = "ac-2-iam-user-mfa-enabled"
|
|
22
|
+
control_ids = extract_control_ids_from_rule_name(rule_name)
|
|
23
|
+
assert "AC-2" in control_ids
|
|
24
|
+
|
|
25
|
+
def test_extract_multiple_controls(self):
|
|
26
|
+
"""Test extracting multiple control IDs."""
|
|
27
|
+
rule_name = "AC-2-AU-3-combined-rule"
|
|
28
|
+
control_ids = extract_control_ids_from_rule_name(rule_name)
|
|
29
|
+
assert "AC-2" in control_ids
|
|
30
|
+
assert "AU-3" in control_ids
|
|
31
|
+
|
|
32
|
+
def test_extract_control_with_enhancement(self):
|
|
33
|
+
"""Test extracting control with enhancement - currently only extracts base control."""
|
|
34
|
+
rule_name = "ia-2-1-mfa-enabled-for-console-access"
|
|
35
|
+
control_ids = extract_control_ids_from_rule_name(rule_name)
|
|
36
|
+
# Note: Current implementation extracts base control only (IA-2), not enhancement (IA-2(1))
|
|
37
|
+
assert "IA-2" in control_ids
|
|
38
|
+
|
|
39
|
+
def test_no_controls_in_name(self):
|
|
40
|
+
"""Test rule name with no control IDs."""
|
|
41
|
+
rule_name = "encrypted-volumes"
|
|
42
|
+
control_ids = extract_control_ids_from_rule_name(rule_name)
|
|
43
|
+
assert len(control_ids) == 0
|
|
44
|
+
|
|
45
|
+
def test_case_insensitive(self):
|
|
46
|
+
"""Test case-insensitive extraction."""
|
|
47
|
+
rule_name = "ac-2-iam-user-mfa-enabled"
|
|
48
|
+
control_ids = extract_control_ids_from_rule_name(rule_name)
|
|
49
|
+
assert "AC-2" in control_ids
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TestExtractControlIdsFromTags:
|
|
53
|
+
"""Test extracting control IDs from tags."""
|
|
54
|
+
|
|
55
|
+
def test_extract_single_control_from_controlid_tag(self):
|
|
56
|
+
"""Test extracting single control from ControlID tag."""
|
|
57
|
+
tags = {"ControlID": "AC-2"}
|
|
58
|
+
control_ids = extract_control_ids_from_tags(tags)
|
|
59
|
+
assert "AC-2" in control_ids
|
|
60
|
+
|
|
61
|
+
def test_extract_multiple_controls_from_controlid_tag(self):
|
|
62
|
+
"""Test extracting multiple controls from ControlID tag."""
|
|
63
|
+
tags = {"ControlID": "AC-2,AU-3,SI-2"}
|
|
64
|
+
control_ids = extract_control_ids_from_tags(tags)
|
|
65
|
+
assert "AC-2" in control_ids
|
|
66
|
+
assert "AU-3" in control_ids
|
|
67
|
+
assert "SI-2" in control_ids
|
|
68
|
+
|
|
69
|
+
def test_extract_from_controlids_tag(self):
|
|
70
|
+
"""Test extracting from ControlIDs (plural) tag."""
|
|
71
|
+
tags = {"ControlIDs": "AC-2,AU-3"}
|
|
72
|
+
control_ids = extract_control_ids_from_tags(tags)
|
|
73
|
+
assert "AC-2" in control_ids
|
|
74
|
+
assert "AU-3" in control_ids
|
|
75
|
+
|
|
76
|
+
def test_extract_from_control_id_hyphen_tag(self):
|
|
77
|
+
"""Test extracting from Control-ID tag."""
|
|
78
|
+
tags = {"Control-ID": "AC-2"}
|
|
79
|
+
control_ids = extract_control_ids_from_tags(tags)
|
|
80
|
+
assert "AC-2" in control_ids
|
|
81
|
+
|
|
82
|
+
def test_no_control_tags(self):
|
|
83
|
+
"""Test tags with no control IDs."""
|
|
84
|
+
tags = {"Environment": "Production", "Owner": "Security"}
|
|
85
|
+
control_ids = extract_control_ids_from_tags(tags)
|
|
86
|
+
assert len(control_ids) == 0
|
|
87
|
+
|
|
88
|
+
def test_empty_tags(self):
|
|
89
|
+
"""Test empty tags dictionary."""
|
|
90
|
+
control_ids = extract_control_ids_from_tags({})
|
|
91
|
+
assert len(control_ids) == 0
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class TestGetControlMappingsForFramework:
|
|
95
|
+
"""Test getting control mappings for framework."""
|
|
96
|
+
|
|
97
|
+
def test_nist_800_53_r5_framework(self):
|
|
98
|
+
"""Test NIST 800-53 R5 framework mappings."""
|
|
99
|
+
mappings = get_control_mappings_for_framework("NIST800-53R5")
|
|
100
|
+
assert len(mappings) > 0
|
|
101
|
+
assert "iam-password-policy" in mappings
|
|
102
|
+
assert "AC-2" in mappings["iam-password-policy"]
|
|
103
|
+
|
|
104
|
+
def test_nist_alternate_formats(self):
|
|
105
|
+
"""Test alternate NIST framework format names."""
|
|
106
|
+
mappings1 = get_control_mappings_for_framework("NIST800-53R5")
|
|
107
|
+
mappings2 = get_control_mappings_for_framework("NIST-800-53-R5")
|
|
108
|
+
mappings3 = get_control_mappings_for_framework("NIST_800_53_R5")
|
|
109
|
+
|
|
110
|
+
assert mappings1 == mappings2 == mappings3
|
|
111
|
+
|
|
112
|
+
def test_unknown_framework(self):
|
|
113
|
+
"""Test unknown framework returns empty mappings."""
|
|
114
|
+
mappings = get_control_mappings_for_framework("UNKNOWN_FRAMEWORK")
|
|
115
|
+
assert len(mappings) == 0
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class TestMapRuleToControls:
|
|
119
|
+
"""Test mapping rules to controls."""
|
|
120
|
+
|
|
121
|
+
def test_map_via_framework_mappings(self):
|
|
122
|
+
"""Test mapping via framework-specific mappings."""
|
|
123
|
+
control_ids = map_rule_to_controls(rule_name="iam-password-policy", framework="NIST800-53R5")
|
|
124
|
+
|
|
125
|
+
assert "AC-2" in control_ids
|
|
126
|
+
assert "IA-5" in control_ids
|
|
127
|
+
|
|
128
|
+
def test_map_via_tags_priority(self):
|
|
129
|
+
"""Test that tags take priority over framework mappings."""
|
|
130
|
+
rule_tags = {"ControlID": "AC-5"}
|
|
131
|
+
control_ids = map_rule_to_controls(
|
|
132
|
+
rule_name="iam-password-policy", rule_tags=rule_tags, framework="NIST800-53R5"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Should include both framework mapping and tag
|
|
136
|
+
assert "AC-2" in control_ids # From framework mapping
|
|
137
|
+
assert "IA-5" in control_ids # From framework mapping
|
|
138
|
+
assert "AC-5" in control_ids # From tags
|
|
139
|
+
|
|
140
|
+
def test_map_via_rule_name_pattern(self):
|
|
141
|
+
"""Test mapping via pattern matching in rule name."""
|
|
142
|
+
control_ids = map_rule_to_controls(rule_name="custom-ac-2-check-rule", framework="NIST800-53R5")
|
|
143
|
+
|
|
144
|
+
assert "AC-2" in control_ids
|
|
145
|
+
|
|
146
|
+
def test_map_via_description_pattern(self):
|
|
147
|
+
"""Test mapping via pattern matching in description."""
|
|
148
|
+
control_ids = map_rule_to_controls(
|
|
149
|
+
rule_name="custom-check", rule_description="This rule checks AC-2 compliance", framework="NIST800-53R5"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
assert "AC-2" in control_ids
|
|
153
|
+
|
|
154
|
+
def test_no_mapping_found(self):
|
|
155
|
+
"""Test rule with no mappable controls."""
|
|
156
|
+
control_ids = map_rule_to_controls(
|
|
157
|
+
rule_name="unknown-custom-rule", rule_description="Some custom check", framework="NIST800-53R5"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
assert len(control_ids) == 0
|
|
161
|
+
|
|
162
|
+
def test_deduplicate_controls(self):
|
|
163
|
+
"""Test that duplicate controls are removed."""
|
|
164
|
+
rule_tags = {"ControlID": "AC-2,AU-3"}
|
|
165
|
+
control_ids = map_rule_to_controls(
|
|
166
|
+
rule_name="ac-2-au-3-combined-check", rule_tags=rule_tags, framework="NIST800-53R5"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Should have AC-2 and AU-3 only once each
|
|
170
|
+
assert control_ids.count("AC-2") == 1
|
|
171
|
+
assert control_ids.count("AU-3") == 1
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class TestNIST80053R5Mappings:
|
|
175
|
+
"""Test NIST 800-53 R5 mappings structure."""
|
|
176
|
+
|
|
177
|
+
def test_mappings_exist(self):
|
|
178
|
+
"""Test that mappings dictionary exists and has content."""
|
|
179
|
+
assert len(NIST_80053_R5_MAPPINGS) > 0
|
|
180
|
+
|
|
181
|
+
def test_cloudtrail_mapping(self):
|
|
182
|
+
"""Test CloudTrail rule mappings."""
|
|
183
|
+
assert "cloudtrail-enabled" in NIST_80053_R5_MAPPINGS
|
|
184
|
+
assert "AU-2" in NIST_80053_R5_MAPPINGS["cloudtrail-enabled"]
|
|
185
|
+
assert "AU-3" in NIST_80053_R5_MAPPINGS["cloudtrail-enabled"]
|
|
186
|
+
|
|
187
|
+
def test_iam_password_policy_mapping(self):
|
|
188
|
+
"""Test IAM password policy mappings."""
|
|
189
|
+
assert "iam-password-policy" in NIST_80053_R5_MAPPINGS
|
|
190
|
+
assert "AC-2" in NIST_80053_R5_MAPPINGS["iam-password-policy"]
|
|
191
|
+
assert "IA-5" in NIST_80053_R5_MAPPINGS["iam-password-policy"]
|
|
192
|
+
|
|
193
|
+
def test_encryption_mappings(self):
|
|
194
|
+
"""Test encryption-related mappings."""
|
|
195
|
+
assert "encrypted-volumes" in NIST_80053_R5_MAPPINGS
|
|
196
|
+
assert "SC-13" in NIST_80053_R5_MAPPINGS["encrypted-volumes"]
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
if __name__ == "__main__":
|
|
200
|
+
pytest.main([__file__, "-v"])
|