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.

Files changed (113) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/utils/app_utils.py +11 -2
  3. regscale/dev/cli.py +26 -0
  4. regscale/dev/version.py +72 -0
  5. regscale/integrations/commercial/__init__.py +15 -1
  6. regscale/integrations/commercial/amazon/amazon/__init__.py +0 -0
  7. regscale/integrations/commercial/amazon/amazon/common.py +204 -0
  8. regscale/integrations/commercial/amazon/common.py +48 -58
  9. regscale/integrations/commercial/aws/audit_manager_compliance.py +2671 -0
  10. regscale/integrations/commercial/aws/cli.py +3093 -55
  11. regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
  12. regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
  13. regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
  14. regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
  15. regscale/integrations/commercial/aws/config_compliance.py +914 -0
  16. regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
  17. regscale/integrations/commercial/aws/evidence_generator.py +283 -0
  18. regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
  19. regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
  20. regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
  21. regscale/integrations/commercial/aws/iam_evidence.py +574 -0
  22. regscale/integrations/commercial/aws/inventory/__init__.py +223 -22
  23. regscale/integrations/commercial/aws/inventory/base.py +107 -5
  24. regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
  25. regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
  26. regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
  27. regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
  28. regscale/integrations/commercial/aws/inventory/resources/compute.py +66 -9
  29. regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
  30. regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
  31. regscale/integrations/commercial/aws/inventory/resources/database.py +106 -31
  32. regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
  33. regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
  34. regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
  35. regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
  36. regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
  37. regscale/integrations/commercial/aws/inventory/resources/networking.py +103 -67
  38. regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
  39. regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
  40. regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
  41. regscale/integrations/commercial/aws/inventory/resources/storage.py +53 -29
  42. regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
  43. regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
  44. regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
  45. regscale/integrations/commercial/aws/kms_evidence.py +879 -0
  46. regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
  47. regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
  48. regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
  49. regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
  50. regscale/integrations/commercial/aws/org_evidence.py +666 -0
  51. regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
  52. regscale/integrations/commercial/aws/s3_evidence.py +632 -0
  53. regscale/integrations/commercial/aws/scanner.py +851 -206
  54. regscale/integrations/commercial/aws/security_hub.py +319 -0
  55. regscale/integrations/commercial/aws/session_manager.py +282 -0
  56. regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
  57. regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
  58. regscale/integrations/commercial/synqly/ticketing.py +27 -0
  59. regscale/integrations/compliance_integration.py +308 -38
  60. regscale/integrations/due_date_handler.py +3 -0
  61. regscale/integrations/scanner_integration.py +399 -84
  62. regscale/models/integration_models/cisa_kev_data.json +65 -5
  63. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  64. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +17 -9
  65. regscale/models/regscale_models/assessment.py +2 -1
  66. regscale/models/regscale_models/control_objective.py +74 -5
  67. regscale/models/regscale_models/file.py +2 -0
  68. regscale/models/regscale_models/issue.py +2 -5
  69. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/METADATA +1 -1
  70. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/RECORD +113 -34
  71. tests/regscale/integrations/commercial/aws/__init__.py +0 -0
  72. tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
  73. tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
  74. tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
  75. tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
  76. tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
  77. tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
  78. tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
  79. tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
  80. tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
  81. tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
  82. tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
  83. tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
  84. tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
  85. tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
  86. tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
  87. tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
  88. tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
  89. tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
  90. tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
  91. tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
  92. tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
  93. tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
  94. tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
  95. tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
  96. tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
  97. tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
  98. tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
  99. tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
  100. tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
  101. tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
  102. tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
  103. tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
  104. tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
  105. tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
  106. tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
  107. tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
  108. tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
  109. tests/regscale/integrations/commercial/test_aws.py +55 -56
  110. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/LICENSE +0 -0
  111. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/WHEEL +0 -0
  112. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.1.0.dist-info}/entry_points.txt +0 -0
  113. {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"])