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.

Files changed (140) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/application.py +1 -0
  3. regscale/core/app/internal/control_editor.py +73 -21
  4. regscale/core/app/internal/login.py +4 -1
  5. regscale/core/app/internal/model_editor.py +219 -64
  6. regscale/core/app/utils/app_utils.py +11 -2
  7. regscale/core/login.py +21 -4
  8. regscale/core/utils/date.py +77 -1
  9. regscale/dev/cli.py +26 -0
  10. regscale/dev/version.py +72 -0
  11. regscale/integrations/commercial/__init__.py +15 -1
  12. regscale/integrations/commercial/amazon/amazon/__init__.py +0 -0
  13. regscale/integrations/commercial/amazon/amazon/common.py +204 -0
  14. regscale/integrations/commercial/amazon/common.py +48 -58
  15. regscale/integrations/commercial/aws/audit_manager_compliance.py +2671 -0
  16. regscale/integrations/commercial/aws/cli.py +3093 -55
  17. regscale/integrations/commercial/aws/cloudtrail_control_mappings.py +333 -0
  18. regscale/integrations/commercial/aws/cloudtrail_evidence.py +501 -0
  19. regscale/integrations/commercial/aws/cloudwatch_control_mappings.py +357 -0
  20. regscale/integrations/commercial/aws/cloudwatch_evidence.py +490 -0
  21. regscale/integrations/commercial/aws/config_compliance.py +914 -0
  22. regscale/integrations/commercial/aws/conformance_pack_mappings.py +198 -0
  23. regscale/integrations/commercial/aws/evidence_generator.py +283 -0
  24. regscale/integrations/commercial/aws/guardduty_control_mappings.py +340 -0
  25. regscale/integrations/commercial/aws/guardduty_evidence.py +1053 -0
  26. regscale/integrations/commercial/aws/iam_control_mappings.py +368 -0
  27. regscale/integrations/commercial/aws/iam_evidence.py +574 -0
  28. regscale/integrations/commercial/aws/inventory/__init__.py +223 -22
  29. regscale/integrations/commercial/aws/inventory/base.py +107 -5
  30. regscale/integrations/commercial/aws/inventory/resources/audit_manager.py +513 -0
  31. regscale/integrations/commercial/aws/inventory/resources/cloudtrail.py +315 -0
  32. regscale/integrations/commercial/aws/inventory/resources/cloudtrail_logs_metadata.py +476 -0
  33. regscale/integrations/commercial/aws/inventory/resources/cloudwatch.py +191 -0
  34. regscale/integrations/commercial/aws/inventory/resources/compute.py +66 -9
  35. regscale/integrations/commercial/aws/inventory/resources/config.py +464 -0
  36. regscale/integrations/commercial/aws/inventory/resources/containers.py +74 -9
  37. regscale/integrations/commercial/aws/inventory/resources/database.py +106 -31
  38. regscale/integrations/commercial/aws/inventory/resources/guardduty.py +286 -0
  39. regscale/integrations/commercial/aws/inventory/resources/iam.py +470 -0
  40. regscale/integrations/commercial/aws/inventory/resources/inspector.py +476 -0
  41. regscale/integrations/commercial/aws/inventory/resources/integration.py +175 -61
  42. regscale/integrations/commercial/aws/inventory/resources/kms.py +447 -0
  43. regscale/integrations/commercial/aws/inventory/resources/networking.py +103 -67
  44. regscale/integrations/commercial/aws/inventory/resources/s3.py +394 -0
  45. regscale/integrations/commercial/aws/inventory/resources/security.py +268 -72
  46. regscale/integrations/commercial/aws/inventory/resources/securityhub.py +473 -0
  47. regscale/integrations/commercial/aws/inventory/resources/storage.py +53 -29
  48. regscale/integrations/commercial/aws/inventory/resources/systems_manager.py +657 -0
  49. regscale/integrations/commercial/aws/inventory/resources/vpc.py +655 -0
  50. regscale/integrations/commercial/aws/kms_control_mappings.py +288 -0
  51. regscale/integrations/commercial/aws/kms_evidence.py +879 -0
  52. regscale/integrations/commercial/aws/ocsf/__init__.py +7 -0
  53. regscale/integrations/commercial/aws/ocsf/constants.py +115 -0
  54. regscale/integrations/commercial/aws/ocsf/mapper.py +435 -0
  55. regscale/integrations/commercial/aws/org_control_mappings.py +286 -0
  56. regscale/integrations/commercial/aws/org_evidence.py +666 -0
  57. regscale/integrations/commercial/aws/s3_control_mappings.py +356 -0
  58. regscale/integrations/commercial/aws/s3_evidence.py +632 -0
  59. regscale/integrations/commercial/aws/scanner.py +853 -205
  60. regscale/integrations/commercial/aws/security_hub.py +319 -0
  61. regscale/integrations/commercial/aws/session_manager.py +282 -0
  62. regscale/integrations/commercial/aws/ssm_control_mappings.py +291 -0
  63. regscale/integrations/commercial/aws/ssm_evidence.py +492 -0
  64. regscale/integrations/commercial/synqly/query_builder.py +4 -1
  65. regscale/integrations/compliance_integration.py +308 -38
  66. regscale/integrations/control_matcher.py +78 -23
  67. regscale/integrations/due_date_handler.py +3 -0
  68. regscale/integrations/public/csam/csam.py +572 -763
  69. regscale/integrations/public/csam/csam_agency_defined.py +179 -0
  70. regscale/integrations/public/csam/csam_common.py +154 -0
  71. regscale/integrations/public/csam/csam_controls.py +432 -0
  72. regscale/integrations/public/csam/csam_poam.py +124 -0
  73. regscale/integrations/public/fedramp/click.py +17 -4
  74. regscale/integrations/public/fedramp/fedramp_cis_crm.py +271 -62
  75. regscale/integrations/public/fedramp/poam/scanner.py +74 -7
  76. regscale/integrations/scanner_integration.py +415 -85
  77. regscale/models/integration_models/cisa_kev_data.json +80 -20
  78. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  79. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +44 -3
  80. regscale/models/integration_models/synqly_models/ocsf_mapper.py +41 -12
  81. regscale/models/platform.py +3 -0
  82. regscale/models/regscale_models/__init__.py +5 -0
  83. regscale/models/regscale_models/assessment.py +2 -1
  84. regscale/models/regscale_models/component.py +1 -1
  85. regscale/models/regscale_models/control_implementation.py +55 -24
  86. regscale/models/regscale_models/control_objective.py +74 -5
  87. regscale/models/regscale_models/file.py +2 -0
  88. regscale/models/regscale_models/issue.py +2 -5
  89. regscale/models/regscale_models/organization.py +3 -0
  90. regscale/models/regscale_models/regscale_model.py +17 -5
  91. regscale/models/regscale_models/security_plan.py +1 -0
  92. regscale/regscale.py +11 -1
  93. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/METADATA +1 -1
  94. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/RECORD +140 -57
  95. tests/regscale/core/test_login.py +171 -4
  96. tests/regscale/integrations/commercial/aws/__init__.py +0 -0
  97. tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
  98. tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
  99. tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
  100. tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
  101. tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
  102. tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
  103. tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
  104. tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
  105. tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
  106. tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
  107. tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
  108. tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
  109. tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
  110. tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
  111. tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
  112. tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
  113. tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
  114. tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
  115. tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
  116. tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
  117. tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
  118. tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
  119. tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
  120. tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
  121. tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
  122. tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
  123. tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
  124. tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
  125. tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
  126. tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
  127. tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
  128. tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
  129. tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
  130. tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
  131. tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
  132. tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
  133. tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
  134. tests/regscale/integrations/commercial/test_aws.py +55 -56
  135. tests/regscale/integrations/test_control_matcher.py +24 -0
  136. tests/regscale/models/test_control_implementation.py +118 -3
  137. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/LICENSE +0 -0
  138. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/WHEEL +0 -0
  139. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/entry_points.txt +0 -0
  140. {regscale_cli-6.27.2.0.dist-info → regscale_cli-6.28.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,452 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Unit tests for AWS CloudTrail Control Mappings."""
4
+
5
+ import pytest
6
+
7
+ from regscale.integrations.commercial.aws.cloudtrail_control_mappings import (
8
+ CLOUDTRAIL_CONTROL_MAPPINGS,
9
+ CloudTrailControlMapper,
10
+ )
11
+
12
+
13
+ class TestCloudTrailControlMapper:
14
+ """Test CloudTrailControlMapper class."""
15
+
16
+ def test_init_default_framework(self):
17
+ """Test initialization with default framework."""
18
+ mapper = CloudTrailControlMapper()
19
+ assert mapper.framework == "NIST800-53R5"
20
+ assert mapper.mappings == CLOUDTRAIL_CONTROL_MAPPINGS
21
+
22
+ def test_init_custom_framework(self):
23
+ """Test initialization with custom framework."""
24
+ mapper = CloudTrailControlMapper(framework="ISO27001")
25
+ assert mapper.framework == "ISO27001"
26
+ assert mapper.mappings == CLOUDTRAIL_CONTROL_MAPPINGS
27
+
28
+ def test_get_mapped_controls(self):
29
+ """Test getting list of mapped controls."""
30
+ mapper = CloudTrailControlMapper()
31
+ controls = mapper.get_mapped_controls()
32
+ assert isinstance(controls, list)
33
+ assert len(controls) > 0
34
+ assert "AU-2" in controls
35
+ assert "AU-3" in controls
36
+ assert "AU-6" in controls
37
+ assert "AU-9" in controls
38
+ assert "AU-11" in controls
39
+ assert "AU-12" in controls
40
+ assert "SI-4" in controls
41
+
42
+ def test_get_control_description_valid(self):
43
+ """Test getting control description for valid control."""
44
+ mapper = CloudTrailControlMapper()
45
+ description = mapper.get_control_description("AU-2")
46
+ assert description is not None
47
+ assert "Event Logging" in description
48
+ assert "Identify the types of events" in description
49
+
50
+ def test_get_control_description_invalid(self):
51
+ """Test getting control description for invalid control."""
52
+ mapper = CloudTrailControlMapper()
53
+ description = mapper.get_control_description("INVALID-CONTROL")
54
+ assert description is None
55
+
56
+ def test_get_check_details_valid(self):
57
+ """Test getting check details for valid control."""
58
+ mapper = CloudTrailControlMapper()
59
+ checks = mapper.get_check_details("AU-2")
60
+ assert checks is not None
61
+ assert isinstance(checks, dict)
62
+ assert "trail_enabled" in checks
63
+ assert "management_events" in checks
64
+ assert checks["trail_enabled"]["weight"] == 100
65
+
66
+ def test_get_check_details_invalid(self):
67
+ """Test getting check details for invalid control."""
68
+ mapper = CloudTrailControlMapper()
69
+ checks = mapper.get_check_details("INVALID-CONTROL")
70
+ assert checks is None
71
+
72
+
73
+ class TestAssessTrailCompliance:
74
+ """Test assess_trail_compliance method."""
75
+
76
+ def test_assess_trail_compliance_all_pass(self):
77
+ """Test assessment when all controls pass."""
78
+ mapper = CloudTrailControlMapper()
79
+ trail_data = {
80
+ "Name": "test-trail",
81
+ "Status": {"IsLogging": True},
82
+ "EventSelectors": [{"IncludeManagementEvents": True, "DataResources": [{"Type": "AWS::S3::Object"}]}],
83
+ "CloudWatchLogsLogGroupArn": "arn:aws:logs:us-east-1:123456789012:log-group:test",
84
+ "LogFileValidationEnabled": True,
85
+ "KmsKeyId": "arn:aws:kms:us-east-1:123456789012:key/test",
86
+ "S3BucketName": "test-bucket",
87
+ "IsMultiRegionTrail": True,
88
+ "SnsTopicARN": "arn:aws:sns:us-east-1:123456789012:test-topic",
89
+ }
90
+ results = mapper.assess_trail_compliance(trail_data)
91
+ assert results["AU-2"] == "PASS"
92
+ assert results["AU-3"] == "PASS"
93
+ assert results["AU-6"] == "PASS"
94
+ assert results["AU-9"] == "PASS"
95
+ assert results["AU-11"] == "PASS"
96
+ assert results["AU-12"] == "PASS"
97
+ assert results["SI-4"] == "PASS"
98
+
99
+ def test_assess_trail_compliance_non_nist_framework(self):
100
+ """Test assessment with non-NIST framework returns empty results."""
101
+ mapper = CloudTrailControlMapper(framework="ISO27001")
102
+ trail_data = {"Name": "test-trail"}
103
+ results = mapper.assess_trail_compliance(trail_data)
104
+ assert results == {}
105
+
106
+
107
+ class TestAssessAU2:
108
+ """Test _assess_au2 (Event Logging) compliance."""
109
+
110
+ def test_au2_pass_logging_enabled_with_management_events(self):
111
+ """Test AU-2 passes when trail is logging with management events."""
112
+ mapper = CloudTrailControlMapper()
113
+ trail_data = {
114
+ "Name": "test-trail",
115
+ "Status": {"IsLogging": True},
116
+ "EventSelectors": [{"IncludeManagementEvents": True}],
117
+ }
118
+ result = mapper._assess_au2(trail_data)
119
+ assert result == "PASS"
120
+
121
+ def test_au2_fail_logging_disabled(self):
122
+ """Test AU-2 fails when trail is not logging."""
123
+ mapper = CloudTrailControlMapper()
124
+ trail_data = {
125
+ "Name": "test-trail",
126
+ "Status": {"IsLogging": False},
127
+ "EventSelectors": [{"IncludeManagementEvents": True}],
128
+ }
129
+ result = mapper._assess_au2(trail_data)
130
+ assert result == "FAIL"
131
+
132
+ def test_au2_fail_no_management_events(self):
133
+ """Test AU-2 fails when management events not enabled."""
134
+ mapper = CloudTrailControlMapper()
135
+ trail_data = {
136
+ "Name": "test-trail",
137
+ "Status": {"IsLogging": True},
138
+ "EventSelectors": [{"IncludeManagementEvents": False}],
139
+ }
140
+ result = mapper._assess_au2(trail_data)
141
+ assert result == "FAIL"
142
+
143
+ def test_au2_fail_empty_event_selectors(self):
144
+ """Test AU-2 fails with empty event selectors."""
145
+ mapper = CloudTrailControlMapper()
146
+ trail_data = {"Name": "test-trail", "Status": {"IsLogging": True}, "EventSelectors": []}
147
+ result = mapper._assess_au2(trail_data)
148
+ assert result == "FAIL"
149
+
150
+ def test_au2_pass_multiple_event_selectors(self):
151
+ """Test AU-2 passes with multiple event selectors where one has management events."""
152
+ mapper = CloudTrailControlMapper()
153
+ trail_data = {
154
+ "Name": "test-trail",
155
+ "Status": {"IsLogging": True},
156
+ "EventSelectors": [
157
+ {"IncludeManagementEvents": False},
158
+ {"IncludeManagementEvents": True},
159
+ ],
160
+ }
161
+ result = mapper._assess_au2(trail_data)
162
+ assert result == "PASS"
163
+
164
+
165
+ class TestAssessAU3:
166
+ """Test _assess_au3 (Content of Audit Records) compliance."""
167
+
168
+ def test_au3_pass_event_selectors_configured(self):
169
+ """Test AU-3 passes when event selectors are configured."""
170
+ mapper = CloudTrailControlMapper()
171
+ trail_data = {"Name": "test-trail", "EventSelectors": [{"IncludeManagementEvents": True}]}
172
+ result = mapper._assess_au3(trail_data)
173
+ assert result == "PASS"
174
+
175
+ def test_au3_fail_no_event_selectors(self):
176
+ """Test AU-3 fails when no event selectors configured."""
177
+ mapper = CloudTrailControlMapper()
178
+ trail_data = {"Name": "test-trail", "EventSelectors": []}
179
+ result = mapper._assess_au3(trail_data)
180
+ assert result == "FAIL"
181
+
182
+ def test_au3_fail_missing_event_selectors(self):
183
+ """Test AU-3 fails when EventSelectors key is missing."""
184
+ mapper = CloudTrailControlMapper()
185
+ trail_data = {"Name": "test-trail"}
186
+ result = mapper._assess_au3(trail_data)
187
+ assert result == "FAIL"
188
+
189
+
190
+ class TestAssessAU6:
191
+ """Test _assess_au6 (Audit Record Review) compliance."""
192
+
193
+ def test_au6_pass_cloudwatch_integration(self):
194
+ """Test AU-6 passes when CloudWatch Logs integration is configured."""
195
+ mapper = CloudTrailControlMapper()
196
+ trail_data = {
197
+ "Name": "test-trail",
198
+ "CloudWatchLogsLogGroupArn": "arn:aws:logs:us-east-1:123456789012:log-group:test",
199
+ }
200
+ result = mapper._assess_au6(trail_data)
201
+ assert result == "PASS"
202
+
203
+ def test_au6_fail_no_cloudwatch_integration(self):
204
+ """Test AU-6 fails when no CloudWatch Logs integration."""
205
+ mapper = CloudTrailControlMapper()
206
+ trail_data = {"Name": "test-trail"}
207
+ result = mapper._assess_au6(trail_data)
208
+ assert result == "FAIL"
209
+
210
+ def test_au6_fail_empty_cloudwatch_arn(self):
211
+ """Test AU-6 fails when CloudWatch ARN is empty."""
212
+ mapper = CloudTrailControlMapper()
213
+ trail_data = {"Name": "test-trail", "CloudWatchLogsLogGroupArn": ""}
214
+ result = mapper._assess_au6(trail_data)
215
+ assert result == "FAIL"
216
+
217
+
218
+ class TestAssessAU9:
219
+ """Test _assess_au9 (Protection of Audit Information) compliance."""
220
+
221
+ def test_au9_pass_log_validation_and_encryption(self):
222
+ """Test AU-9 passes when log validation and encryption are enabled."""
223
+ mapper = CloudTrailControlMapper()
224
+ trail_data = {
225
+ "Name": "test-trail",
226
+ "LogFileValidationEnabled": True,
227
+ "KmsKeyId": "arn:aws:kms:us-east-1:123456789012:key/test",
228
+ }
229
+ result = mapper._assess_au9(trail_data)
230
+ assert result == "PASS"
231
+
232
+ def test_au9_pass_log_validation_without_encryption(self):
233
+ """Test AU-9 passes when log validation enabled but no encryption (recommended)."""
234
+ mapper = CloudTrailControlMapper()
235
+ trail_data = {"Name": "test-trail", "LogFileValidationEnabled": True}
236
+ result = mapper._assess_au9(trail_data)
237
+ assert result == "PASS"
238
+
239
+ def test_au9_fail_no_log_validation(self):
240
+ """Test AU-9 fails when log file validation not enabled."""
241
+ mapper = CloudTrailControlMapper()
242
+ trail_data = {"Name": "test-trail", "LogFileValidationEnabled": False}
243
+ result = mapper._assess_au9(trail_data)
244
+ assert result == "FAIL"
245
+
246
+ def test_au9_fail_missing_log_validation_key(self):
247
+ """Test AU-9 fails when LogFileValidationEnabled key is missing."""
248
+ mapper = CloudTrailControlMapper()
249
+ trail_data = {"Name": "test-trail"}
250
+ result = mapper._assess_au9(trail_data)
251
+ assert result == "FAIL"
252
+
253
+
254
+ class TestAssessAU11:
255
+ """Test _assess_au11 (Audit Record Retention) compliance."""
256
+
257
+ def test_au11_pass_s3_bucket_configured(self):
258
+ """Test AU-11 passes when S3 bucket is configured."""
259
+ mapper = CloudTrailControlMapper()
260
+ trail_data = {"Name": "test-trail", "S3BucketName": "test-bucket"}
261
+ result = mapper._assess_au11(trail_data)
262
+ assert result == "PASS"
263
+
264
+ def test_au11_fail_no_s3_bucket(self):
265
+ """Test AU-11 fails when no S3 bucket configured."""
266
+ mapper = CloudTrailControlMapper()
267
+ trail_data = {"Name": "test-trail"}
268
+ result = mapper._assess_au11(trail_data)
269
+ assert result == "FAIL"
270
+
271
+ def test_au11_fail_empty_s3_bucket(self):
272
+ """Test AU-11 fails when S3 bucket name is empty."""
273
+ mapper = CloudTrailControlMapper()
274
+ trail_data = {"Name": "test-trail", "S3BucketName": ""}
275
+ result = mapper._assess_au11(trail_data)
276
+ assert result == "FAIL"
277
+
278
+
279
+ class TestAssessAU12:
280
+ """Test _assess_au12 (Audit Record Generation) compliance."""
281
+
282
+ def test_au12_pass_multi_region_with_data_events(self):
283
+ """Test AU-12 passes when multi-region trail with data events."""
284
+ mapper = CloudTrailControlMapper()
285
+ trail_data = {
286
+ "Name": "test-trail",
287
+ "IsMultiRegionTrail": True,
288
+ "EventSelectors": [{"DataResources": [{"Type": "AWS::S3::Object"}]}],
289
+ }
290
+ result = mapper._assess_au12(trail_data)
291
+ assert result == "PASS"
292
+
293
+ def test_au12_pass_multi_region_without_data_events(self):
294
+ """Test AU-12 passes when multi-region trail without data events (recommended)."""
295
+ mapper = CloudTrailControlMapper()
296
+ trail_data = {"Name": "test-trail", "IsMultiRegionTrail": True, "EventSelectors": []}
297
+ result = mapper._assess_au12(trail_data)
298
+ assert result == "PASS"
299
+
300
+ def test_au12_fail_not_multi_region(self):
301
+ """Test AU-12 fails when not a multi-region trail."""
302
+ mapper = CloudTrailControlMapper()
303
+ trail_data = {"Name": "test-trail", "IsMultiRegionTrail": False, "EventSelectors": []}
304
+ result = mapper._assess_au12(trail_data)
305
+ assert result == "FAIL"
306
+
307
+ def test_au12_fail_missing_multi_region_key(self):
308
+ """Test AU-12 fails when IsMultiRegionTrail key is missing."""
309
+ mapper = CloudTrailControlMapper()
310
+ trail_data = {"Name": "test-trail"}
311
+ result = mapper._assess_au12(trail_data)
312
+ assert result == "FAIL"
313
+
314
+
315
+ class TestAssessSI4:
316
+ """Test _assess_si4 (System Monitoring) compliance."""
317
+
318
+ def test_si4_pass_sns_notifications_configured(self):
319
+ """Test SI-4 passes when SNS notifications are configured."""
320
+ mapper = CloudTrailControlMapper()
321
+ trail_data = {"Name": "test-trail", "SnsTopicARN": "arn:aws:sns:us-east-1:123456789012:test-topic"}
322
+ result = mapper._assess_si4(trail_data)
323
+ assert result == "PASS"
324
+
325
+ def test_si4_fail_no_sns_notifications(self):
326
+ """Test SI-4 fails when no SNS notifications configured."""
327
+ mapper = CloudTrailControlMapper()
328
+ trail_data = {"Name": "test-trail"}
329
+ result = mapper._assess_si4(trail_data)
330
+ assert result == "FAIL"
331
+
332
+ def test_si4_fail_empty_sns_arn(self):
333
+ """Test SI-4 fails when SNS ARN is empty."""
334
+ mapper = CloudTrailControlMapper()
335
+ trail_data = {"Name": "test-trail", "SnsTopicARN": ""}
336
+ result = mapper._assess_si4(trail_data)
337
+ assert result == "FAIL"
338
+
339
+
340
+ class TestAssessAllTrailsCompliance:
341
+ """Test assess_all_trails_compliance method."""
342
+
343
+ def test_assess_empty_trails_list(self):
344
+ """Test assessment with empty trails list."""
345
+ mapper = CloudTrailControlMapper()
346
+ results = mapper.assess_all_trails_compliance([])
347
+ assert results["AU-2"] == "FAIL"
348
+ assert results["AU-3"] == "FAIL"
349
+ assert results["AU-6"] == "FAIL"
350
+ assert results["AU-9"] == "FAIL"
351
+ assert results["AU-11"] == "FAIL"
352
+ assert results["AU-12"] == "FAIL"
353
+ assert results["SI-4"] == "FAIL"
354
+
355
+ def test_assess_single_trail_all_pass(self):
356
+ """Test assessment with single trail that passes all controls."""
357
+ mapper = CloudTrailControlMapper()
358
+ trail_data = {
359
+ "Name": "test-trail",
360
+ "Status": {"IsLogging": True},
361
+ "EventSelectors": [{"IncludeManagementEvents": True}],
362
+ "CloudWatchLogsLogGroupArn": "arn:aws:logs:us-east-1:123456789012:log-group:test",
363
+ "LogFileValidationEnabled": True,
364
+ "S3BucketName": "test-bucket",
365
+ "IsMultiRegionTrail": True,
366
+ "SnsTopicARN": "arn:aws:sns:us-east-1:123456789012:test-topic",
367
+ }
368
+ results = mapper.assess_all_trails_compliance([trail_data])
369
+ assert results["AU-2"] == "PASS"
370
+ assert results["AU-3"] == "PASS"
371
+ assert results["AU-6"] == "PASS"
372
+ assert results["AU-9"] == "PASS"
373
+ assert results["AU-11"] == "PASS"
374
+ assert results["AU-12"] == "PASS"
375
+ assert results["SI-4"] == "PASS"
376
+
377
+ def test_assess_multiple_trails_aggregate_pass(self):
378
+ """Test assessment with multiple trails where aggregate results pass."""
379
+ mapper = CloudTrailControlMapper()
380
+ trail1 = {
381
+ "Name": "trail1",
382
+ "Status": {"IsLogging": True},
383
+ "EventSelectors": [{"IncludeManagementEvents": True}],
384
+ "CloudWatchLogsLogGroupArn": "arn:aws:logs:us-east-1:123456789012:log-group:test",
385
+ "LogFileValidationEnabled": True,
386
+ "S3BucketName": "test-bucket",
387
+ "IsMultiRegionTrail": False,
388
+ "SnsTopicARN": "arn:aws:sns:us-east-1:123456789012:test-topic",
389
+ }
390
+ trail2 = {
391
+ "Name": "trail2",
392
+ "Status": {"IsLogging": True},
393
+ "EventSelectors": [{"IncludeManagementEvents": True}],
394
+ "CloudWatchLogsLogGroupArn": "",
395
+ "LogFileValidationEnabled": True,
396
+ "S3BucketName": "test-bucket",
397
+ "IsMultiRegionTrail": True,
398
+ "SnsTopicARN": "",
399
+ }
400
+ results = mapper.assess_all_trails_compliance([trail1, trail2])
401
+ assert results["AU-2"] == "PASS"
402
+ assert results["AU-12"] == "PASS"
403
+
404
+ def test_assess_multiple_trails_all_fail(self):
405
+ """Test assessment with multiple trails where all fail a control."""
406
+ mapper = CloudTrailControlMapper()
407
+ trail1 = {"Name": "trail1", "Status": {"IsLogging": False}, "EventSelectors": []}
408
+ trail2 = {"Name": "trail2", "Status": {"IsLogging": False}, "EventSelectors": []}
409
+ results = mapper.assess_all_trails_compliance([trail1, trail2])
410
+ assert results["AU-2"] == "FAIL"
411
+
412
+
413
+ class TestCloudTrailControlMappings:
414
+ """Test CLOUDTRAIL_CONTROL_MAPPINGS structure."""
415
+
416
+ def test_mappings_exist(self):
417
+ """Test that mappings dictionary exists and has content."""
418
+ assert len(CLOUDTRAIL_CONTROL_MAPPINGS) > 0
419
+
420
+ def test_au2_mapping_structure(self):
421
+ """Test AU-2 mapping structure."""
422
+ assert "AU-2" in CLOUDTRAIL_CONTROL_MAPPINGS
423
+ au2 = CLOUDTRAIL_CONTROL_MAPPINGS["AU-2"]
424
+ assert "name" in au2
425
+ assert "description" in au2
426
+ assert "checks" in au2
427
+ assert "trail_enabled" in au2["checks"]
428
+ assert "management_events" in au2["checks"]
429
+
430
+ def test_au9_mapping_structure(self):
431
+ """Test AU-9 mapping structure with multiple checks."""
432
+ assert "AU-9" in CLOUDTRAIL_CONTROL_MAPPINGS
433
+ au9 = CLOUDTRAIL_CONTROL_MAPPINGS["AU-9"]
434
+ assert len(au9["checks"]) >= 2
435
+ assert "log_file_validation" in au9["checks"]
436
+ assert "s3_encryption" in au9["checks"]
437
+
438
+ def test_all_controls_have_required_fields(self):
439
+ """Test all control mappings have required fields."""
440
+ for control_id, control_data in CLOUDTRAIL_CONTROL_MAPPINGS.items():
441
+ assert "name" in control_data
442
+ assert "description" in control_data
443
+ assert "checks" in control_data
444
+ assert isinstance(control_data["checks"], dict)
445
+ for check_name, check_data in control_data["checks"].items():
446
+ assert "weight" in check_data
447
+ assert "pass_criteria" in check_data
448
+ assert "fail_criteria" in check_data
449
+
450
+
451
+ if __name__ == "__main__":
452
+ pytest.main([__file__, "-v"])