regscale-cli 6.27.3.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 (112) 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/compliance_integration.py +308 -38
  59. regscale/integrations/due_date_handler.py +3 -0
  60. regscale/integrations/scanner_integration.py +399 -84
  61. regscale/models/integration_models/cisa_kev_data.json +34 -4
  62. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  63. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +17 -9
  64. regscale/models/regscale_models/assessment.py +2 -1
  65. regscale/models/regscale_models/control_objective.py +74 -5
  66. regscale/models/regscale_models/file.py +2 -0
  67. regscale/models/regscale_models/issue.py +2 -5
  68. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/METADATA +1 -1
  69. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/RECORD +112 -33
  70. tests/regscale/integrations/commercial/aws/__init__.py +0 -0
  71. tests/regscale/integrations/commercial/aws/test_audit_manager_compliance.py +1304 -0
  72. tests/regscale/integrations/commercial/aws/test_audit_manager_evidence_aggregation.py +341 -0
  73. tests/regscale/integrations/commercial/aws/test_aws_audit_manager_collector.py +1155 -0
  74. tests/regscale/integrations/commercial/aws/test_aws_cloudtrail_collector.py +534 -0
  75. tests/regscale/integrations/commercial/aws/test_aws_config_collector.py +400 -0
  76. tests/regscale/integrations/commercial/aws/test_aws_guardduty_collector.py +315 -0
  77. tests/regscale/integrations/commercial/aws/test_aws_iam_collector.py +458 -0
  78. tests/regscale/integrations/commercial/aws/test_aws_inspector_collector.py +353 -0
  79. tests/regscale/integrations/commercial/aws/test_aws_inventory_integration.py +530 -0
  80. tests/regscale/integrations/commercial/aws/test_aws_kms_collector.py +919 -0
  81. tests/regscale/integrations/commercial/aws/test_aws_s3_collector.py +722 -0
  82. tests/regscale/integrations/commercial/aws/test_aws_scanner_integration.py +722 -0
  83. tests/regscale/integrations/commercial/aws/test_aws_securityhub_collector.py +792 -0
  84. tests/regscale/integrations/commercial/aws/test_aws_systems_manager_collector.py +918 -0
  85. tests/regscale/integrations/commercial/aws/test_aws_vpc_collector.py +996 -0
  86. tests/regscale/integrations/commercial/aws/test_cli_evidence.py +431 -0
  87. tests/regscale/integrations/commercial/aws/test_cloudtrail_control_mappings.py +452 -0
  88. tests/regscale/integrations/commercial/aws/test_cloudtrail_evidence.py +788 -0
  89. tests/regscale/integrations/commercial/aws/test_config_compliance.py +298 -0
  90. tests/regscale/integrations/commercial/aws/test_conformance_pack_mappings.py +200 -0
  91. tests/regscale/integrations/commercial/aws/test_evidence_generator.py +386 -0
  92. tests/regscale/integrations/commercial/aws/test_guardduty_control_mappings.py +564 -0
  93. tests/regscale/integrations/commercial/aws/test_guardduty_evidence.py +1041 -0
  94. tests/regscale/integrations/commercial/aws/test_iam_control_mappings.py +718 -0
  95. tests/regscale/integrations/commercial/aws/test_iam_evidence.py +1375 -0
  96. tests/regscale/integrations/commercial/aws/test_kms_control_mappings.py +656 -0
  97. tests/regscale/integrations/commercial/aws/test_kms_evidence.py +1163 -0
  98. tests/regscale/integrations/commercial/aws/test_ocsf_mapper.py +370 -0
  99. tests/regscale/integrations/commercial/aws/test_org_control_mappings.py +546 -0
  100. tests/regscale/integrations/commercial/aws/test_org_evidence.py +1240 -0
  101. tests/regscale/integrations/commercial/aws/test_s3_control_mappings.py +672 -0
  102. tests/regscale/integrations/commercial/aws/test_s3_evidence.py +987 -0
  103. tests/regscale/integrations/commercial/aws/test_scanner_evidence.py +373 -0
  104. tests/regscale/integrations/commercial/aws/test_security_hub_config_filtering.py +539 -0
  105. tests/regscale/integrations/commercial/aws/test_session_manager.py +516 -0
  106. tests/regscale/integrations/commercial/aws/test_ssm_control_mappings.py +588 -0
  107. tests/regscale/integrations/commercial/aws/test_ssm_evidence.py +735 -0
  108. tests/regscale/integrations/commercial/test_aws.py +55 -56
  109. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/LICENSE +0 -0
  110. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/WHEEL +0 -0
  111. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/entry_points.txt +0 -0
  112. {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,546 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Unit tests for AWS Organizations Control Mappings."""
4
+
5
+ import pytest
6
+
7
+ from regscale.integrations.commercial.aws.org_control_mappings import (
8
+ COMPLIANT_ACCOUNT_STATUSES,
9
+ ISO_27001_MAPPINGS,
10
+ ORG_CONTROL_MAPPINGS,
11
+ OrgControlMapper,
12
+ RESTRICTIVE_SCP_PATTERNS,
13
+ )
14
+
15
+
16
+ class TestOrgControlMappings:
17
+ """Test Organizations control mappings constants."""
18
+
19
+ def test_org_control_mappings_exist(self):
20
+ """Test that Organizations control mappings dictionary exists and has content."""
21
+ assert len(ORG_CONTROL_MAPPINGS) > 0
22
+ assert "AC-1" in ORG_CONTROL_MAPPINGS
23
+ assert "PM-9" in ORG_CONTROL_MAPPINGS
24
+ assert "AC-2" in ORG_CONTROL_MAPPINGS
25
+ assert "AC-6" in ORG_CONTROL_MAPPINGS
26
+
27
+ def test_ac1_mapping_structure(self):
28
+ """Test AC-1 mapping structure."""
29
+ ac1 = ORG_CONTROL_MAPPINGS["AC-1"]
30
+ assert "name" in ac1
31
+ assert "description" in ac1
32
+ assert "checks" in ac1
33
+ assert "scp_attached" in ac1["checks"]
34
+ assert "organizational_structure" in ac1["checks"]
35
+
36
+ def test_pm9_mapping_structure(self):
37
+ """Test PM-9 mapping structure."""
38
+ pm9 = ORG_CONTROL_MAPPINGS["PM-9"]
39
+ assert "name" in pm9
40
+ assert "description" in pm9
41
+ assert "checks" in pm9
42
+ assert "account_governance" in pm9["checks"]
43
+ assert "policy_enforcement" in pm9["checks"]
44
+
45
+ def test_ac2_mapping_structure(self):
46
+ """Test AC-2 mapping structure."""
47
+ ac2 = ORG_CONTROL_MAPPINGS["AC-2"]
48
+ assert "name" in ac2
49
+ assert "description" in ac2
50
+ assert "checks" in ac2
51
+ assert "active_accounts" in ac2["checks"]
52
+ assert "account_tracking" in ac2["checks"]
53
+
54
+ def test_ac6_mapping_structure(self):
55
+ """Test AC-6 mapping structure."""
56
+ ac6 = ORG_CONTROL_MAPPINGS["AC-6"]
57
+ assert "name" in ac6
58
+ assert "description" in ac6
59
+ assert "checks" in ac6
60
+ assert "restrictive_scps" in ac6["checks"]
61
+
62
+ def test_iso_27001_mappings_exist(self):
63
+ """Test ISO 27001 mappings exist."""
64
+ assert len(ISO_27001_MAPPINGS) > 0
65
+ assert "A.6.1.1" in ISO_27001_MAPPINGS
66
+ assert "A.6.1.2" in ISO_27001_MAPPINGS
67
+
68
+ def test_restrictive_scp_patterns(self):
69
+ """Test restrictive SCP patterns list."""
70
+ assert "DenyAllOutsideRegion" in RESTRICTIVE_SCP_PATTERNS
71
+ assert "DenyRootAccount" in RESTRICTIVE_SCP_PATTERNS
72
+ assert "RequireMFA" in RESTRICTIVE_SCP_PATTERNS
73
+ assert "DenyCloudTrailDelete" in RESTRICTIVE_SCP_PATTERNS
74
+
75
+ def test_compliant_account_statuses(self):
76
+ """Test compliant account statuses list."""
77
+ assert "ACTIVE" in COMPLIANT_ACCOUNT_STATUSES
78
+
79
+
80
+ class TestOrgControlMapperInitialization:
81
+ """Test OrgControlMapper initialization."""
82
+
83
+ def test_init_with_nist_framework(self):
84
+ """Test initialization with NIST framework."""
85
+ mapper = OrgControlMapper(framework="NIST800-53R5")
86
+ assert mapper.framework == "NIST800-53R5"
87
+ assert mapper.mappings == ORG_CONTROL_MAPPINGS
88
+
89
+ def test_init_with_iso_framework(self):
90
+ """Test initialization with ISO framework."""
91
+ mapper = OrgControlMapper(framework="ISO27001")
92
+ assert mapper.framework == "ISO27001"
93
+ assert mapper.mappings == ISO_27001_MAPPINGS
94
+
95
+ def test_init_default_framework(self):
96
+ """Test initialization with default framework."""
97
+ mapper = OrgControlMapper()
98
+ assert mapper.framework == "NIST800-53R5"
99
+ assert mapper.mappings == ORG_CONTROL_MAPPINGS
100
+
101
+
102
+ class TestAssessOrganizationCompliance:
103
+ """Test assess_organization_compliance method."""
104
+
105
+ def test_assess_compliant_organization(self):
106
+ """Test assessing a compliant organization."""
107
+ mapper = OrgControlMapper()
108
+ org_data = {
109
+ "service_control_policies": [
110
+ {"Name": "FullAWSAccess"},
111
+ {"Name": "DenyRootAccount"},
112
+ {"Name": "RestrictRegions"},
113
+ ],
114
+ "organizational_units": [{"Name": "Root"}, {"Name": "Production"}, {"Name": "Development"}],
115
+ "accounts": [
116
+ {"Id": "123456789012", "Status": "ACTIVE", "Email": "prod@example.com"},
117
+ {"Id": "123456789013", "Status": "ACTIVE", "Email": "dev@example.com"},
118
+ ],
119
+ }
120
+
121
+ results = mapper.assess_organization_compliance(org_data)
122
+
123
+ assert results["AC-1"] == "PASS"
124
+ assert results["PM-9"] == "PASS"
125
+ assert results["AC-2"] == "PASS"
126
+ assert results["AC-6"] == "PASS"
127
+
128
+ def test_assess_organization_no_scps(self):
129
+ """Test assessing organization with no restrictive SCPs."""
130
+ mapper = OrgControlMapper()
131
+ org_data = {
132
+ "service_control_policies": [{"Name": "FullAWSAccess"}],
133
+ "organizational_units": [{"Name": "Root"}, {"Name": "Production"}],
134
+ "accounts": [{"Id": "123456789012", "Status": "ACTIVE", "Email": "prod@example.com"}],
135
+ }
136
+
137
+ results = mapper.assess_organization_compliance(org_data)
138
+
139
+ assert results["AC-1"] == "FAIL"
140
+ assert results["PM-9"] == "FAIL"
141
+ assert results["AC-2"] == "PASS"
142
+ assert results["AC-6"] == "FAIL"
143
+
144
+ def test_assess_organization_flat_structure(self):
145
+ """Test assessing organization with flat structure."""
146
+ mapper = OrgControlMapper()
147
+ org_data = {
148
+ "service_control_policies": [{"Name": "DenyRootAccount"}],
149
+ "organizational_units": [{"Name": "Root"}],
150
+ "accounts": [{"Id": "123456789012", "Status": "ACTIVE", "Email": "admin@example.com"}],
151
+ }
152
+
153
+ results = mapper.assess_organization_compliance(org_data)
154
+
155
+ assert results["AC-1"] == "FAIL"
156
+ assert results["PM-9"] == "FAIL"
157
+
158
+ def test_assess_organization_with_inactive_accounts(self):
159
+ """Test assessing organization with inactive accounts."""
160
+ mapper = OrgControlMapper()
161
+ org_data = {
162
+ "service_control_policies": [{"Name": "DenyRootAccount"}],
163
+ "organizational_units": [{"Name": "Root"}, {"Name": "Production"}],
164
+ "accounts": [
165
+ {"Id": "123456789012", "Status": "ACTIVE", "Email": "prod@example.com"},
166
+ {"Id": "123456789013", "Status": "SUSPENDED", "Email": "old@example.com"},
167
+ ],
168
+ }
169
+
170
+ results = mapper.assess_organization_compliance(org_data)
171
+
172
+ assert results["AC-2"] == "FAIL"
173
+
174
+
175
+ class TestAssessAC1:
176
+ """Test _assess_ac1 method."""
177
+
178
+ def test_organization_with_restrictive_scps_and_ous_passes(self):
179
+ """Test organization with restrictive SCPs and OUs passes."""
180
+ mapper = OrgControlMapper()
181
+ org_data = {
182
+ "service_control_policies": [{"Name": "FullAWSAccess"}, {"Name": "DenyRootAccount"}],
183
+ "organizational_units": [{"Name": "Root"}, {"Name": "Production"}],
184
+ }
185
+
186
+ result = mapper._assess_ac1(org_data)
187
+ assert result == "PASS"
188
+
189
+ def test_organization_without_restrictive_scps_fails(self):
190
+ """Test organization without restrictive SCPs fails."""
191
+ mapper = OrgControlMapper()
192
+ org_data = {
193
+ "service_control_policies": [{"Name": "FullAWSAccess"}],
194
+ "organizational_units": [{"Name": "Root"}, {"Name": "Production"}],
195
+ }
196
+
197
+ result = mapper._assess_ac1(org_data)
198
+ assert result == "FAIL"
199
+
200
+ def test_organization_without_ous_fails(self):
201
+ """Test organization without organizational units fails."""
202
+ mapper = OrgControlMapper()
203
+ org_data = {
204
+ "service_control_policies": [{"Name": "DenyRootAccount"}],
205
+ "organizational_units": [{"Name": "Root"}],
206
+ }
207
+
208
+ result = mapper._assess_ac1(org_data)
209
+ assert result == "FAIL"
210
+
211
+ def test_organization_with_empty_scps_list(self):
212
+ """Test organization with empty SCPs list."""
213
+ mapper = OrgControlMapper()
214
+ org_data = {
215
+ "service_control_policies": [],
216
+ "organizational_units": [{"Name": "Root"}, {"Name": "Production"}],
217
+ }
218
+
219
+ result = mapper._assess_ac1(org_data)
220
+ assert result == "FAIL"
221
+
222
+
223
+ class TestAssessPM9:
224
+ """Test _assess_pm9 method."""
225
+
226
+ def test_organization_with_env_separation_and_restrictive_scps_passes(self):
227
+ """Test organization with environment separation and restrictive SCPs passes."""
228
+ mapper = OrgControlMapper()
229
+ org_data = {
230
+ "organizational_units": [{"Name": "Root"}, {"Name": "Production"}, {"Name": "Development"}],
231
+ "service_control_policies": [{"Name": "RestrictRegions"}],
232
+ }
233
+
234
+ result = mapper._assess_pm9(org_data)
235
+ assert result == "PASS"
236
+
237
+ def test_organization_with_prod_ou_passes(self):
238
+ """Test organization with prod OU passes."""
239
+ mapper = OrgControlMapper()
240
+ org_data = {
241
+ "organizational_units": [{"Name": "Root"}, {"Name": "Prod"}],
242
+ "service_control_policies": [{"Name": "DenyLeaveOrganization"}],
243
+ }
244
+
245
+ result = mapper._assess_pm9(org_data)
246
+ assert result == "PASS"
247
+
248
+ def test_organization_with_sandbox_ou_passes(self):
249
+ """Test organization with sandbox OU passes."""
250
+ mapper = OrgControlMapper()
251
+ org_data = {
252
+ "organizational_units": [{"Name": "Root"}, {"Name": "Sandbox"}],
253
+ "service_control_policies": [{"Name": "RequireMFA"}],
254
+ }
255
+
256
+ result = mapper._assess_pm9(org_data)
257
+ assert result == "PASS"
258
+
259
+ def test_organization_without_env_separation_fails(self):
260
+ """Test organization without environment separation fails."""
261
+ mapper = OrgControlMapper()
262
+ org_data = {
263
+ "organizational_units": [{"Name": "Root"}, {"Name": "Applications"}],
264
+ "service_control_policies": [{"Name": "RestrictRegions"}],
265
+ }
266
+
267
+ result = mapper._assess_pm9(org_data)
268
+ assert result == "FAIL"
269
+
270
+ def test_organization_without_restrictive_scps_fails(self):
271
+ """Test organization without restrictive SCPs fails."""
272
+ mapper = OrgControlMapper()
273
+ org_data = {
274
+ "organizational_units": [{"Name": "Root"}, {"Name": "Production"}],
275
+ "service_control_policies": [{"Name": "FullAWSAccess"}],
276
+ }
277
+
278
+ result = mapper._assess_pm9(org_data)
279
+ assert result == "FAIL"
280
+
281
+ def test_organization_with_test_ou_passes(self):
282
+ """Test organization with test OU passes."""
283
+ mapper = OrgControlMapper()
284
+ org_data = {
285
+ "organizational_units": [{"Name": "Root"}, {"Name": "Test"}],
286
+ "service_control_policies": [{"Name": "DenyGuardDutyDisable"}],
287
+ }
288
+
289
+ result = mapper._assess_pm9(org_data)
290
+ assert result == "PASS"
291
+
292
+
293
+ class TestAssessAC2:
294
+ """Test _assess_ac2 method."""
295
+
296
+ def test_organization_with_all_active_accounts_passes(self):
297
+ """Test organization with all active accounts passes."""
298
+ mapper = OrgControlMapper()
299
+ org_data = {
300
+ "accounts": [
301
+ {"Id": "123456789012", "Status": "ACTIVE", "Email": "account1@example.com"},
302
+ {"Id": "123456789013", "Status": "ACTIVE", "Email": "account2@example.com"},
303
+ ]
304
+ }
305
+
306
+ result = mapper._assess_ac2(org_data)
307
+ assert result == "PASS"
308
+
309
+ def test_organization_with_no_accounts_fails(self):
310
+ """Test organization with no accounts fails."""
311
+ mapper = OrgControlMapper()
312
+ org_data = {"accounts": []}
313
+
314
+ result = mapper._assess_ac2(org_data)
315
+ assert result == "FAIL"
316
+
317
+ def test_organization_with_suspended_accounts_fails(self):
318
+ """Test organization with suspended accounts fails."""
319
+ mapper = OrgControlMapper()
320
+ org_data = {
321
+ "accounts": [
322
+ {"Id": "123456789012", "Status": "ACTIVE", "Email": "active@example.com"},
323
+ {"Id": "123456789013", "Status": "SUSPENDED", "Email": "suspended@example.com"},
324
+ ]
325
+ }
326
+
327
+ result = mapper._assess_ac2(org_data)
328
+ assert result == "FAIL"
329
+
330
+ def test_organization_with_accounts_missing_email_fails(self):
331
+ """Test organization with accounts missing email fails."""
332
+ mapper = OrgControlMapper()
333
+ org_data = {
334
+ "accounts": [
335
+ {"Id": "123456789012", "Status": "ACTIVE", "Email": "account1@example.com"},
336
+ {"Id": "123456789013", "Status": "ACTIVE", "Email": ""},
337
+ ]
338
+ }
339
+
340
+ result = mapper._assess_ac2(org_data)
341
+ assert result == "FAIL"
342
+
343
+ def test_organization_with_accounts_without_email_key_fails(self):
344
+ """Test organization with accounts without email key fails."""
345
+ mapper = OrgControlMapper()
346
+ org_data = {"accounts": [{"Id": "123456789012", "Status": "ACTIVE"}]}
347
+
348
+ result = mapper._assess_ac2(org_data)
349
+ assert result == "FAIL"
350
+
351
+
352
+ class TestAssessAC6:
353
+ """Test _assess_ac6 method."""
354
+
355
+ def test_organization_with_restrictive_scps_passes(self):
356
+ """Test organization with restrictive SCPs passes."""
357
+ mapper = OrgControlMapper()
358
+ org_data = {
359
+ "service_control_policies": [{"Name": "FullAWSAccess"}, {"Name": "DenyRootAccount"}],
360
+ }
361
+
362
+ result = mapper._assess_ac6(org_data)
363
+ assert result == "PASS"
364
+
365
+ def test_organization_with_deny_in_scp_name_passes(self):
366
+ """Test organization with Deny in SCP name passes."""
367
+ mapper = OrgControlMapper()
368
+ org_data = {
369
+ "service_control_policies": [{"Name": "FullAWSAccess"}, {"Name": "DenyS3PublicAccess"}],
370
+ }
371
+
372
+ result = mapper._assess_ac6(org_data)
373
+ assert result == "PASS"
374
+
375
+ def test_organization_with_only_full_access_fails(self):
376
+ """Test organization with only FullAWSAccess SCP fails."""
377
+ mapper = OrgControlMapper()
378
+ org_data = {
379
+ "service_control_policies": [{"Name": "FullAWSAccess"}],
380
+ }
381
+
382
+ result = mapper._assess_ac6(org_data)
383
+ assert result == "FAIL"
384
+
385
+ def test_organization_without_restrictive_scps_fails(self):
386
+ """Test organization without restrictive SCPs fails."""
387
+ mapper = OrgControlMapper()
388
+ org_data = {
389
+ "service_control_policies": [{"Name": "FullAWSAccess"}, {"Name": "AllowAllServices"}],
390
+ }
391
+
392
+ result = mapper._assess_ac6(org_data)
393
+ assert result == "FAIL"
394
+
395
+ def test_organization_with_restrictregions_passes(self):
396
+ """Test organization with RestrictRegions SCP passes."""
397
+ mapper = OrgControlMapper()
398
+ org_data = {
399
+ "service_control_policies": [{"Name": "RestrictRegions"}],
400
+ }
401
+
402
+ result = mapper._assess_ac6(org_data)
403
+ assert result == "PASS"
404
+
405
+
406
+ class TestGetControlDescription:
407
+ """Test get_control_description method."""
408
+
409
+ def test_get_ac1_description(self):
410
+ """Test getting AC-1 description."""
411
+ mapper = OrgControlMapper()
412
+ description = mapper.get_control_description("AC-1")
413
+
414
+ assert description is not None
415
+ assert "Policy and Procedures" in description
416
+
417
+ def test_get_pm9_description(self):
418
+ """Test getting PM-9 description."""
419
+ mapper = OrgControlMapper()
420
+ description = mapper.get_control_description("PM-9")
421
+
422
+ assert description is not None
423
+ assert "Risk Management Strategy" in description
424
+
425
+ def test_get_ac2_description(self):
426
+ """Test getting AC-2 description."""
427
+ mapper = OrgControlMapper()
428
+ description = mapper.get_control_description("AC-2")
429
+
430
+ assert description is not None
431
+ assert "Account Management" in description
432
+
433
+ def test_get_ac6_description(self):
434
+ """Test getting AC-6 description."""
435
+ mapper = OrgControlMapper()
436
+ description = mapper.get_control_description("AC-6")
437
+
438
+ assert description is not None
439
+ assert "Least Privilege" in description
440
+
441
+ def test_get_unknown_control_description(self):
442
+ """Test getting description for unknown control."""
443
+ mapper = OrgControlMapper()
444
+ description = mapper.get_control_description("UNKNOWN-1")
445
+
446
+ assert description is None
447
+
448
+ def test_get_iso_control_description(self):
449
+ """Test getting ISO control description."""
450
+ mapper = OrgControlMapper(framework="ISO27001")
451
+ description = mapper.get_control_description("A.6.1.1")
452
+
453
+ assert description is not None
454
+ assert "security roles" in description.lower()
455
+
456
+
457
+ class TestGetMappedControls:
458
+ """Test get_mapped_controls method."""
459
+
460
+ def test_get_nist_controls(self):
461
+ """Test getting NIST controls."""
462
+ mapper = OrgControlMapper()
463
+ controls = mapper.get_mapped_controls()
464
+
465
+ assert len(controls) > 0
466
+ assert "AC-1" in controls
467
+ assert "PM-9" in controls
468
+ assert "AC-2" in controls
469
+ assert "AC-6" in controls
470
+
471
+ def test_get_iso_controls(self):
472
+ """Test getting ISO controls."""
473
+ mapper = OrgControlMapper(framework="ISO27001")
474
+ controls = mapper.get_mapped_controls()
475
+
476
+ assert len(controls) > 0
477
+ assert "A.6.1.1" in controls
478
+ assert "A.6.1.2" in controls
479
+
480
+ def test_controls_are_unique(self):
481
+ """Test that returned controls are unique."""
482
+ mapper = OrgControlMapper()
483
+ controls = mapper.get_mapped_controls()
484
+
485
+ assert len(controls) == len(set(controls))
486
+
487
+
488
+ class TestGetCheckDetails:
489
+ """Test get_check_details method."""
490
+
491
+ def test_get_ac1_check_details(self):
492
+ """Test getting AC-1 check details."""
493
+ mapper = OrgControlMapper()
494
+ details = mapper.get_check_details("AC-1")
495
+
496
+ assert details is not None
497
+ assert "scp_attached" in details
498
+ assert "organizational_structure" in details
499
+ assert details["scp_attached"]["weight"] == 100
500
+
501
+ def test_get_pm9_check_details(self):
502
+ """Test getting PM-9 check details."""
503
+ mapper = OrgControlMapper()
504
+ details = mapper.get_check_details("PM-9")
505
+
506
+ assert details is not None
507
+ assert "account_governance" in details
508
+ assert "policy_enforcement" in details
509
+
510
+ def test_get_ac2_check_details(self):
511
+ """Test getting AC-2 check details."""
512
+ mapper = OrgControlMapper()
513
+ details = mapper.get_check_details("AC-2")
514
+
515
+ assert details is not None
516
+ assert "active_accounts" in details
517
+ assert "account_tracking" in details
518
+
519
+ def test_get_ac6_check_details(self):
520
+ """Test getting AC-6 check details."""
521
+ mapper = OrgControlMapper()
522
+ details = mapper.get_check_details("AC-6")
523
+
524
+ assert details is not None
525
+ assert "restrictive_scps" in details
526
+
527
+ def test_get_unknown_control_check_details(self):
528
+ """Test getting check details for unknown control."""
529
+ mapper = OrgControlMapper()
530
+ details = mapper.get_check_details("UNKNOWN-1")
531
+
532
+ assert details is None
533
+
534
+ def test_check_details_structure(self):
535
+ """Test check details have required structure."""
536
+ mapper = OrgControlMapper()
537
+ details = mapper.get_check_details("AC-1")
538
+
539
+ for check_name, check_data in details.items():
540
+ assert "weight" in check_data
541
+ assert "pass_criteria" in check_data
542
+ assert "fail_criteria" in check_data
543
+
544
+
545
+ if __name__ == "__main__":
546
+ pytest.main([__file__, "-v"])