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,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"])
|