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,1240 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Unit tests for AWS Organizations Evidence Integration."""
|
|
4
|
+
|
|
5
|
+
import gzip
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import time
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
from io import BytesIO
|
|
11
|
+
from unittest.mock import MagicMock, Mock, patch, mock_open
|
|
12
|
+
|
|
13
|
+
import pytest
|
|
14
|
+
from botocore.exceptions import ClientError
|
|
15
|
+
|
|
16
|
+
from regscale.integrations.commercial.aws.org_evidence import (
|
|
17
|
+
OrgComplianceItem,
|
|
18
|
+
AWSOrganizationsEvidenceIntegration,
|
|
19
|
+
ORG_CACHE_FILE,
|
|
20
|
+
CACHE_TTL_SECONDS,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
PATH = "regscale.integrations.commercial.aws.org_evidence"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TestOrgComplianceItem:
|
|
27
|
+
"""Test cases for OrgComplianceItem class."""
|
|
28
|
+
|
|
29
|
+
def setup_method(self):
|
|
30
|
+
"""Set up test fixtures."""
|
|
31
|
+
self.mock_mapper = MagicMock()
|
|
32
|
+
self.mock_mapper.framework = "NIST800-53R5"
|
|
33
|
+
|
|
34
|
+
def test_init_with_complete_data(self):
|
|
35
|
+
"""Test initialization with complete organization data."""
|
|
36
|
+
org_data = {
|
|
37
|
+
"Id": "o-abc123xyz456",
|
|
38
|
+
"Arn": "arn:aws:organizations::123456789012:organization/o-abc123xyz456",
|
|
39
|
+
"MasterAccountId": "123456789012",
|
|
40
|
+
"accounts": [
|
|
41
|
+
{"Id": "111111111111", "Name": "Account1", "Status": "ACTIVE", "Email": "account1@example.com"},
|
|
42
|
+
{"Id": "222222222222", "Name": "Account2", "Status": "ACTIVE", "Email": "account2@example.com"},
|
|
43
|
+
],
|
|
44
|
+
"organizational_units": [
|
|
45
|
+
{"Id": "ou-abc1-11111111", "Name": "Production"},
|
|
46
|
+
{"Id": "ou-abc1-22222222", "Name": "Development"},
|
|
47
|
+
],
|
|
48
|
+
"service_control_policies": [
|
|
49
|
+
{"Id": "p-FullAWSAccess", "Name": "FullAWSAccess", "Type": "SERVICE_CONTROL_POLICY"},
|
|
50
|
+
{"Id": "p-DenyS3", "Name": "DenyS3Access", "Type": "SERVICE_CONTROL_POLICY"},
|
|
51
|
+
],
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
self.mock_mapper.assess_organization_compliance.return_value = {
|
|
55
|
+
"AC-1": "PASS",
|
|
56
|
+
"PM-9": "PASS",
|
|
57
|
+
"AC-2": "PASS",
|
|
58
|
+
"AC-6": "PASS",
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
item = OrgComplianceItem(org_data, self.mock_mapper)
|
|
62
|
+
|
|
63
|
+
assert item.org_data == org_data
|
|
64
|
+
assert item.control_mapper == self.mock_mapper
|
|
65
|
+
assert item._org_id == "o-abc123xyz456"
|
|
66
|
+
assert item._org_arn == "arn:aws:organizations::123456789012:organization/o-abc123xyz456"
|
|
67
|
+
assert item._master_account_id == "123456789012"
|
|
68
|
+
assert len(item._accounts) == 2
|
|
69
|
+
assert len(item._ous) == 2
|
|
70
|
+
assert len(item._scps) == 2
|
|
71
|
+
assert item._compliance_results == self.mock_mapper.assess_organization_compliance.return_value
|
|
72
|
+
|
|
73
|
+
def test_init_with_minimal_data(self):
|
|
74
|
+
"""Test initialization with minimal organization data."""
|
|
75
|
+
org_data = {}
|
|
76
|
+
|
|
77
|
+
self.mock_mapper.assess_organization_compliance.return_value = {}
|
|
78
|
+
|
|
79
|
+
item = OrgComplianceItem(org_data, self.mock_mapper)
|
|
80
|
+
|
|
81
|
+
assert item._org_id == ""
|
|
82
|
+
assert item._org_arn == ""
|
|
83
|
+
assert item._master_account_id == ""
|
|
84
|
+
assert len(item._accounts) == 0
|
|
85
|
+
assert len(item._ous) == 0
|
|
86
|
+
assert len(item._scps) == 0
|
|
87
|
+
|
|
88
|
+
def test_resource_id_property(self):
|
|
89
|
+
"""Test resource_id property."""
|
|
90
|
+
org_data = {"Id": "o-testorg123"}
|
|
91
|
+
self.mock_mapper.assess_organization_compliance.return_value = {}
|
|
92
|
+
|
|
93
|
+
item = OrgComplianceItem(org_data, self.mock_mapper)
|
|
94
|
+
|
|
95
|
+
assert item.resource_id == "o-testorg123"
|
|
96
|
+
|
|
97
|
+
def test_resource_name_property(self):
|
|
98
|
+
"""Test resource_name property."""
|
|
99
|
+
org_data = {"Id": "o-abc123xyz456789"}
|
|
100
|
+
self.mock_mapper.assess_organization_compliance.return_value = {}
|
|
101
|
+
|
|
102
|
+
item = OrgComplianceItem(org_data, self.mock_mapper)
|
|
103
|
+
|
|
104
|
+
assert item.resource_name == "AWS Organization o-abc123xyz4..."
|
|
105
|
+
|
|
106
|
+
def test_control_id_property_with_failure(self):
|
|
107
|
+
"""Test control_id property returns first failed control."""
|
|
108
|
+
org_data = {}
|
|
109
|
+
self.mock_mapper.assess_organization_compliance.return_value = {
|
|
110
|
+
"AC-1": "PASS",
|
|
111
|
+
"PM-9": "FAIL",
|
|
112
|
+
"AC-2": "PASS",
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
item = OrgComplianceItem(org_data, self.mock_mapper)
|
|
116
|
+
|
|
117
|
+
assert item.control_id == "PM-9"
|
|
118
|
+
|
|
119
|
+
def test_control_id_property_all_pass(self):
|
|
120
|
+
"""Test control_id property when all controls pass."""
|
|
121
|
+
org_data = {}
|
|
122
|
+
self.mock_mapper.assess_organization_compliance.return_value = {
|
|
123
|
+
"AC-1": "PASS",
|
|
124
|
+
"PM-9": "PASS",
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
item = OrgComplianceItem(org_data, self.mock_mapper)
|
|
128
|
+
|
|
129
|
+
assert item.control_id == "AC-1"
|
|
130
|
+
|
|
131
|
+
def test_control_id_property_empty_results(self):
|
|
132
|
+
"""Test control_id property with no compliance results."""
|
|
133
|
+
org_data = {}
|
|
134
|
+
self.mock_mapper.assess_organization_compliance.return_value = {}
|
|
135
|
+
|
|
136
|
+
item = OrgComplianceItem(org_data, self.mock_mapper)
|
|
137
|
+
|
|
138
|
+
assert item.control_id == "AC-1"
|
|
139
|
+
|
|
140
|
+
def test_compliance_result_property_pass(self):
|
|
141
|
+
"""Test compliance_result property when all checks pass."""
|
|
142
|
+
org_data = {}
|
|
143
|
+
self.mock_mapper.assess_organization_compliance.return_value = {
|
|
144
|
+
"AC-1": "PASS",
|
|
145
|
+
"PM-9": "PASS",
|
|
146
|
+
"AC-2": "PASS",
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
item = OrgComplianceItem(org_data, self.mock_mapper)
|
|
150
|
+
|
|
151
|
+
assert item.compliance_result == "PASS"
|
|
152
|
+
|
|
153
|
+
def test_compliance_result_property_fail(self):
|
|
154
|
+
"""Test compliance_result property when any check fails."""
|
|
155
|
+
org_data = {}
|
|
156
|
+
self.mock_mapper.assess_organization_compliance.return_value = {
|
|
157
|
+
"AC-1": "PASS",
|
|
158
|
+
"PM-9": "FAIL",
|
|
159
|
+
"AC-2": "PASS",
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
item = OrgComplianceItem(org_data, self.mock_mapper)
|
|
163
|
+
|
|
164
|
+
assert item.compliance_result == "FAIL"
|
|
165
|
+
|
|
166
|
+
def test_compliance_result_property_empty(self):
|
|
167
|
+
"""Test compliance_result property with no results."""
|
|
168
|
+
org_data = {}
|
|
169
|
+
self.mock_mapper.assess_organization_compliance.return_value = {}
|
|
170
|
+
|
|
171
|
+
item = OrgComplianceItem(org_data, self.mock_mapper)
|
|
172
|
+
|
|
173
|
+
assert item.compliance_result == "PASS"
|
|
174
|
+
|
|
175
|
+
def test_severity_property_pass(self):
|
|
176
|
+
"""Test severity property when compliance passes."""
|
|
177
|
+
org_data = {}
|
|
178
|
+
self.mock_mapper.assess_organization_compliance.return_value = {
|
|
179
|
+
"AC-1": "PASS",
|
|
180
|
+
"PM-9": "PASS",
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
item = OrgComplianceItem(org_data, self.mock_mapper)
|
|
184
|
+
|
|
185
|
+
assert item.severity is None
|
|
186
|
+
|
|
187
|
+
def test_severity_property_high_ac1_fail(self):
|
|
188
|
+
"""Test severity property for AC-1 failures."""
|
|
189
|
+
org_data = {}
|
|
190
|
+
self.mock_mapper.assess_organization_compliance.return_value = {
|
|
191
|
+
"AC-1": "FAIL",
|
|
192
|
+
"PM-9": "PASS",
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
item = OrgComplianceItem(org_data, self.mock_mapper)
|
|
196
|
+
|
|
197
|
+
assert item.severity == "HIGH"
|
|
198
|
+
|
|
199
|
+
def test_severity_property_high_pm9_fail(self):
|
|
200
|
+
"""Test severity property for PM-9 failures."""
|
|
201
|
+
org_data = {}
|
|
202
|
+
self.mock_mapper.assess_organization_compliance.return_value = {
|
|
203
|
+
"AC-1": "PASS",
|
|
204
|
+
"PM-9": "FAIL",
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
item = OrgComplianceItem(org_data, self.mock_mapper)
|
|
208
|
+
|
|
209
|
+
assert item.severity == "HIGH"
|
|
210
|
+
|
|
211
|
+
def test_severity_property_medium_ac6_fail(self):
|
|
212
|
+
"""Test severity property for AC-6 failures."""
|
|
213
|
+
org_data = {}
|
|
214
|
+
self.mock_mapper.assess_organization_compliance.return_value = {
|
|
215
|
+
"AC-1": "PASS",
|
|
216
|
+
"PM-9": "PASS",
|
|
217
|
+
"AC-6": "FAIL",
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
item = OrgComplianceItem(org_data, self.mock_mapper)
|
|
221
|
+
|
|
222
|
+
assert item.severity == "MEDIUM"
|
|
223
|
+
|
|
224
|
+
def test_severity_property_medium_ac2_fail(self):
|
|
225
|
+
"""Test severity property for AC-2 failures."""
|
|
226
|
+
org_data = {}
|
|
227
|
+
self.mock_mapper.assess_organization_compliance.return_value = {
|
|
228
|
+
"AC-1": "PASS",
|
|
229
|
+
"AC-2": "FAIL",
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
item = OrgComplianceItem(org_data, self.mock_mapper)
|
|
233
|
+
|
|
234
|
+
assert item.severity == "MEDIUM"
|
|
235
|
+
|
|
236
|
+
def test_description_property_pass(self):
|
|
237
|
+
"""Test description property with passing compliance."""
|
|
238
|
+
org_data = {
|
|
239
|
+
"Id": "o-testorg",
|
|
240
|
+
"Arn": "arn:aws:organizations::123456789012:organization/o-testorg",
|
|
241
|
+
"MasterAccountId": "123456789012",
|
|
242
|
+
"accounts": [{"Id": "111111111111"}],
|
|
243
|
+
"organizational_units": [{"Id": "ou-1"}],
|
|
244
|
+
"service_control_policies": [{"Id": "p-1"}],
|
|
245
|
+
}
|
|
246
|
+
self.mock_mapper.assess_organization_compliance.return_value = {
|
|
247
|
+
"AC-1": "PASS",
|
|
248
|
+
"PM-9": "PASS",
|
|
249
|
+
}
|
|
250
|
+
self.mock_mapper.get_control_description.side_effect = lambda x: f"{x} Description"
|
|
251
|
+
|
|
252
|
+
item = OrgComplianceItem(org_data, self.mock_mapper)
|
|
253
|
+
description = item.description
|
|
254
|
+
|
|
255
|
+
assert "AWS Organizations Governance Assessment" in description
|
|
256
|
+
assert "o-testorg" in description
|
|
257
|
+
assert "123456789012" in description
|
|
258
|
+
assert "Total Accounts:</strong> 1" in description
|
|
259
|
+
assert "Organizational Units:</strong> 1" in description
|
|
260
|
+
assert "Service Control Policies:</strong> 1" in description
|
|
261
|
+
assert "AC-1" in description
|
|
262
|
+
assert "PM-9" in description
|
|
263
|
+
assert "PASS" in description
|
|
264
|
+
assert "Remediation Guidance" not in description
|
|
265
|
+
|
|
266
|
+
def test_description_property_fail_with_remediation(self):
|
|
267
|
+
"""Test description property with failing compliance and remediation guidance."""
|
|
268
|
+
org_data = {
|
|
269
|
+
"Id": "o-testorg",
|
|
270
|
+
"Arn": "arn:aws:organizations::123456789012:organization/o-testorg",
|
|
271
|
+
"MasterAccountId": "123456789012",
|
|
272
|
+
"accounts": [
|
|
273
|
+
{"Id": "111111111111", "Status": "ACTIVE"},
|
|
274
|
+
{"Id": "222222222222", "Status": "SUSPENDED"},
|
|
275
|
+
],
|
|
276
|
+
"organizational_units": [{"Id": "ou-root"}],
|
|
277
|
+
"service_control_policies": [{"Id": "p-FullAWSAccess", "Name": "FullAWSAccess"}],
|
|
278
|
+
}
|
|
279
|
+
self.mock_mapper.assess_organization_compliance.return_value = {
|
|
280
|
+
"AC-1": "FAIL",
|
|
281
|
+
"PM-9": "FAIL",
|
|
282
|
+
"AC-2": "FAIL",
|
|
283
|
+
"AC-6": "FAIL",
|
|
284
|
+
}
|
|
285
|
+
self.mock_mapper.get_control_description.side_effect = lambda x: f"{x} Description"
|
|
286
|
+
|
|
287
|
+
item = OrgComplianceItem(org_data, self.mock_mapper)
|
|
288
|
+
description = item.description
|
|
289
|
+
|
|
290
|
+
assert "FAIL" in description
|
|
291
|
+
assert "Remediation Guidance" in description
|
|
292
|
+
assert "Create organizational units (OUs) for governance structure" in description
|
|
293
|
+
assert "Attach Service Control Policies to enforce access controls" in description
|
|
294
|
+
assert "Organize accounts by risk profile" in description
|
|
295
|
+
assert "Review and activate or remove 1 suspended accounts" in description
|
|
296
|
+
assert "Implement least privilege SCPs" in description
|
|
297
|
+
|
|
298
|
+
def test_framework_property(self):
|
|
299
|
+
"""Test framework property."""
|
|
300
|
+
org_data = {}
|
|
301
|
+
self.mock_mapper.assess_organization_compliance.return_value = {}
|
|
302
|
+
self.mock_mapper.framework = "NIST800-53R5"
|
|
303
|
+
|
|
304
|
+
item = OrgComplianceItem(org_data, self.mock_mapper)
|
|
305
|
+
|
|
306
|
+
assert item.framework == "NIST800-53R5"
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
class TestAWSOrganizationsEvidenceIntegrationInit:
|
|
310
|
+
"""Test cases for AWSOrganizationsEvidenceIntegration initialization."""
|
|
311
|
+
|
|
312
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
313
|
+
@patch(f"{PATH}.boto3.Session")
|
|
314
|
+
def test_init_with_defaults(self, mock_session_class, mock_mapper_class):
|
|
315
|
+
"""Test initialization with default parameters."""
|
|
316
|
+
mock_session = MagicMock()
|
|
317
|
+
mock_client = MagicMock()
|
|
318
|
+
mock_session.client.return_value = mock_client
|
|
319
|
+
mock_session_class.return_value = mock_session
|
|
320
|
+
|
|
321
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123)
|
|
322
|
+
|
|
323
|
+
assert integration.plan_id == 123
|
|
324
|
+
assert integration.region == "us-east-1"
|
|
325
|
+
assert integration.title == "AWS Organizations"
|
|
326
|
+
assert integration.collect_evidence is False
|
|
327
|
+
assert integration.evidence_as_attachments is True
|
|
328
|
+
assert integration.evidence_control_ids is None
|
|
329
|
+
assert integration.evidence_frequency == 30
|
|
330
|
+
assert integration.force_refresh is False
|
|
331
|
+
assert integration.create_issues is True
|
|
332
|
+
assert integration.update_control_status is True
|
|
333
|
+
assert integration.create_poams is False
|
|
334
|
+
assert integration.parent_module == "securityplans"
|
|
335
|
+
|
|
336
|
+
mock_session_class.assert_called_once_with(profile_name=None, region_name="us-east-1")
|
|
337
|
+
mock_mapper_class.assert_called_once_with(framework="NIST800-53R5")
|
|
338
|
+
|
|
339
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
340
|
+
@patch(f"{PATH}.boto3.Session")
|
|
341
|
+
def test_init_with_explicit_credentials(self, mock_session_class, mock_mapper_class):
|
|
342
|
+
"""Test initialization with explicit AWS credentials."""
|
|
343
|
+
mock_session = MagicMock()
|
|
344
|
+
mock_client = MagicMock()
|
|
345
|
+
mock_session.client.return_value = mock_client
|
|
346
|
+
mock_session_class.return_value = mock_session
|
|
347
|
+
|
|
348
|
+
integration = AWSOrganizationsEvidenceIntegration(
|
|
349
|
+
plan_id=456,
|
|
350
|
+
region="us-west-2",
|
|
351
|
+
aws_access_key_id="AKIATEST",
|
|
352
|
+
aws_secret_access_key="secret",
|
|
353
|
+
aws_session_token="token",
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
assert integration.region == "us-west-2"
|
|
357
|
+
mock_session_class.assert_called_once_with(
|
|
358
|
+
region_name="us-west-2",
|
|
359
|
+
aws_access_key_id="AKIATEST",
|
|
360
|
+
aws_secret_access_key="secret",
|
|
361
|
+
aws_session_token="token",
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
365
|
+
@patch(f"{PATH}.boto3.Session")
|
|
366
|
+
def test_init_with_profile(self, mock_session_class, mock_mapper_class):
|
|
367
|
+
"""Test initialization with AWS profile."""
|
|
368
|
+
mock_session = MagicMock()
|
|
369
|
+
mock_client = MagicMock()
|
|
370
|
+
mock_session.client.return_value = mock_client
|
|
371
|
+
mock_session_class.return_value = mock_session
|
|
372
|
+
|
|
373
|
+
AWSOrganizationsEvidenceIntegration(plan_id=789, region="eu-west-1", profile="test-profile") # noqa: F841
|
|
374
|
+
|
|
375
|
+
mock_session_class.assert_called_once_with(profile_name="test-profile", region_name="eu-west-1")
|
|
376
|
+
|
|
377
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
378
|
+
@patch(f"{PATH}.boto3.Session")
|
|
379
|
+
def test_init_with_all_options(self, mock_session_class, mock_mapper_class):
|
|
380
|
+
"""Test initialization with all optional parameters."""
|
|
381
|
+
mock_session = MagicMock()
|
|
382
|
+
mock_client = MagicMock()
|
|
383
|
+
mock_session.client.return_value = mock_client
|
|
384
|
+
mock_session_class.return_value = mock_session
|
|
385
|
+
|
|
386
|
+
integration = AWSOrganizationsEvidenceIntegration(
|
|
387
|
+
plan_id=999,
|
|
388
|
+
region="ap-southeast-1",
|
|
389
|
+
framework="ISO27001",
|
|
390
|
+
create_issues=False,
|
|
391
|
+
update_control_status=False,
|
|
392
|
+
create_poams=True,
|
|
393
|
+
parent_module="assessments",
|
|
394
|
+
collect_evidence=True,
|
|
395
|
+
evidence_as_attachments=False,
|
|
396
|
+
evidence_control_ids=["AC-1", "PM-9"],
|
|
397
|
+
evidence_frequency=60,
|
|
398
|
+
force_refresh=True,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
assert integration.plan_id == 999
|
|
402
|
+
assert integration.region == "ap-southeast-1"
|
|
403
|
+
assert integration.framework == "ISO27001"
|
|
404
|
+
assert integration.create_issues is False
|
|
405
|
+
assert integration.update_control_status is False
|
|
406
|
+
assert integration.create_poams is True
|
|
407
|
+
assert integration.collect_evidence is True
|
|
408
|
+
assert integration.evidence_as_attachments is False
|
|
409
|
+
assert integration.evidence_control_ids == ["AC-1", "PM-9"]
|
|
410
|
+
assert integration.evidence_frequency == 60
|
|
411
|
+
assert integration.force_refresh is True
|
|
412
|
+
|
|
413
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
414
|
+
@patch(f"{PATH}.boto3.Session")
|
|
415
|
+
def test_init_client_creation_failure(self, mock_session_class, mock_mapper_class):
|
|
416
|
+
"""Test initialization when Organizations client creation fails."""
|
|
417
|
+
mock_session = MagicMock()
|
|
418
|
+
mock_session.client.side_effect = Exception("Failed to create Organizations client")
|
|
419
|
+
mock_session_class.return_value = mock_session
|
|
420
|
+
|
|
421
|
+
with pytest.raises(Exception) as exc_info:
|
|
422
|
+
AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
423
|
+
|
|
424
|
+
assert "Failed to create Organizations client" in str(exc_info.value)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
class TestCacheManagement:
|
|
428
|
+
"""Test cases for cache management methods."""
|
|
429
|
+
|
|
430
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
431
|
+
@patch(f"{PATH}.boto3.Session")
|
|
432
|
+
@patch(f"{PATH}.os.path.exists")
|
|
433
|
+
def test_is_cache_valid_no_file(self, mock_exists, mock_session_class, mock_mapper_class):
|
|
434
|
+
"""Test cache validation when file does not exist."""
|
|
435
|
+
mock_exists.return_value = False
|
|
436
|
+
mock_session = MagicMock()
|
|
437
|
+
mock_session.client.return_value = MagicMock()
|
|
438
|
+
mock_session_class.return_value = mock_session
|
|
439
|
+
|
|
440
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
441
|
+
assert integration._is_cache_valid() is False
|
|
442
|
+
|
|
443
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
444
|
+
@patch(f"{PATH}.boto3.Session")
|
|
445
|
+
@patch(f"{PATH}.os.path.exists")
|
|
446
|
+
@patch(f"{PATH}.os.path.getmtime")
|
|
447
|
+
@patch(f"{PATH}.time.time")
|
|
448
|
+
def test_is_cache_valid_expired(self, mock_time, mock_getmtime, mock_exists, mock_session_class, mock_mapper_class):
|
|
449
|
+
"""Test cache validation when cache is expired."""
|
|
450
|
+
mock_exists.return_value = True
|
|
451
|
+
mock_time.return_value = 1000000
|
|
452
|
+
mock_getmtime.return_value = 1000000 - CACHE_TTL_SECONDS - 100
|
|
453
|
+
mock_session = MagicMock()
|
|
454
|
+
mock_session.client.return_value = MagicMock()
|
|
455
|
+
mock_session_class.return_value = mock_session
|
|
456
|
+
|
|
457
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
458
|
+
assert integration._is_cache_valid() is False
|
|
459
|
+
|
|
460
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
461
|
+
@patch(f"{PATH}.boto3.Session")
|
|
462
|
+
@patch(f"{PATH}.os.path.exists")
|
|
463
|
+
@patch(f"{PATH}.os.path.getmtime")
|
|
464
|
+
@patch(f"{PATH}.time.time")
|
|
465
|
+
def test_is_cache_valid_fresh(self, mock_time, mock_getmtime, mock_exists, mock_session_class, mock_mapper_class):
|
|
466
|
+
"""Test cache validation when cache is fresh."""
|
|
467
|
+
mock_exists.return_value = True
|
|
468
|
+
mock_time.return_value = 1000000
|
|
469
|
+
mock_getmtime.return_value = 1000000 - 1000
|
|
470
|
+
mock_session = MagicMock()
|
|
471
|
+
mock_session.client.return_value = MagicMock()
|
|
472
|
+
mock_session_class.return_value = mock_session
|
|
473
|
+
|
|
474
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
475
|
+
assert integration._is_cache_valid() is True
|
|
476
|
+
|
|
477
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
478
|
+
@patch(f"{PATH}.boto3.Session")
|
|
479
|
+
def test_load_cached_data_success(self, mock_session_class, mock_mapper_class):
|
|
480
|
+
"""Test loading cached data successfully."""
|
|
481
|
+
mock_session = MagicMock()
|
|
482
|
+
mock_session.client.return_value = MagicMock()
|
|
483
|
+
mock_session_class.return_value = mock_session
|
|
484
|
+
|
|
485
|
+
test_data = {
|
|
486
|
+
"Id": "o-testorg",
|
|
487
|
+
"accounts": [{"Id": "111111111111"}],
|
|
488
|
+
"organizational_units": [],
|
|
489
|
+
"service_control_policies": [],
|
|
490
|
+
}
|
|
491
|
+
mock_file = mock_open(read_data=json.dumps(test_data))
|
|
492
|
+
|
|
493
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
494
|
+
|
|
495
|
+
with patch("builtins.open", mock_file):
|
|
496
|
+
result = integration._load_cached_data()
|
|
497
|
+
|
|
498
|
+
assert result == test_data
|
|
499
|
+
|
|
500
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
501
|
+
@patch(f"{PATH}.boto3.Session")
|
|
502
|
+
def test_load_cached_data_json_error(self, mock_session_class, mock_mapper_class):
|
|
503
|
+
"""Test loading cached data with JSON decode error."""
|
|
504
|
+
mock_session = MagicMock()
|
|
505
|
+
mock_session.client.return_value = MagicMock()
|
|
506
|
+
mock_session_class.return_value = mock_session
|
|
507
|
+
|
|
508
|
+
mock_file = mock_open(read_data="invalid json")
|
|
509
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
510
|
+
|
|
511
|
+
with patch("builtins.open", mock_file):
|
|
512
|
+
result = integration._load_cached_data()
|
|
513
|
+
|
|
514
|
+
assert result == {}
|
|
515
|
+
|
|
516
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
517
|
+
@patch(f"{PATH}.boto3.Session")
|
|
518
|
+
def test_load_cached_data_io_error(self, mock_session_class, mock_mapper_class):
|
|
519
|
+
"""Test loading cached data with IO error."""
|
|
520
|
+
mock_session = MagicMock()
|
|
521
|
+
mock_session.client.return_value = MagicMock()
|
|
522
|
+
mock_session_class.return_value = mock_session
|
|
523
|
+
|
|
524
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
525
|
+
|
|
526
|
+
with patch("builtins.open", side_effect=IOError("File not found")):
|
|
527
|
+
result = integration._load_cached_data()
|
|
528
|
+
|
|
529
|
+
assert result == {}
|
|
530
|
+
|
|
531
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
532
|
+
@patch(f"{PATH}.boto3.Session")
|
|
533
|
+
@patch(f"{PATH}.os.makedirs")
|
|
534
|
+
def test_save_to_cache_success(self, mock_makedirs, mock_session_class, mock_mapper_class):
|
|
535
|
+
"""Test saving data to cache successfully."""
|
|
536
|
+
mock_session = MagicMock()
|
|
537
|
+
mock_session.client.return_value = MagicMock()
|
|
538
|
+
mock_session_class.return_value = mock_session
|
|
539
|
+
|
|
540
|
+
test_data = {"Id": "o-testorg", "accounts": [], "organizational_units": [], "service_control_policies": []}
|
|
541
|
+
mock_file = mock_open()
|
|
542
|
+
|
|
543
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
544
|
+
|
|
545
|
+
with patch("builtins.open", mock_file):
|
|
546
|
+
integration._save_to_cache(test_data)
|
|
547
|
+
|
|
548
|
+
mock_makedirs.assert_called_once()
|
|
549
|
+
mock_file.assert_called_once()
|
|
550
|
+
|
|
551
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
552
|
+
@patch(f"{PATH}.boto3.Session")
|
|
553
|
+
@patch(f"{PATH}.os.makedirs")
|
|
554
|
+
def test_save_to_cache_io_error(self, mock_makedirs, mock_session_class, mock_mapper_class):
|
|
555
|
+
"""Test saving data to cache with IO error."""
|
|
556
|
+
mock_session = MagicMock()
|
|
557
|
+
mock_session.client.return_value = MagicMock()
|
|
558
|
+
mock_session_class.return_value = mock_session
|
|
559
|
+
|
|
560
|
+
test_data = {"Id": "o-testorg"}
|
|
561
|
+
|
|
562
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
563
|
+
|
|
564
|
+
with patch("builtins.open", side_effect=IOError("Permission denied")):
|
|
565
|
+
integration._save_to_cache(test_data)
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
class TestFetchOrganizationData:
|
|
569
|
+
"""Test cases for fetching organization data."""
|
|
570
|
+
|
|
571
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
572
|
+
@patch(f"{PATH}.boto3.Session")
|
|
573
|
+
def test_list_organizational_units_success(self, mock_session_class, mock_mapper_class):
|
|
574
|
+
"""Test listing organizational units successfully."""
|
|
575
|
+
mock_client = MagicMock()
|
|
576
|
+
mock_paginator = MagicMock()
|
|
577
|
+
mock_paginator.paginate.side_effect = [
|
|
578
|
+
[{"OrganizationalUnits": [{"Id": "ou-1", "Name": "OU1"}]}],
|
|
579
|
+
[{"OrganizationalUnits": []}],
|
|
580
|
+
]
|
|
581
|
+
mock_client.get_paginator.return_value = mock_paginator
|
|
582
|
+
mock_client.list_roots.return_value = {"Roots": [{"Id": "r-root"}]}
|
|
583
|
+
|
|
584
|
+
mock_session = MagicMock()
|
|
585
|
+
mock_session.client.return_value = mock_client
|
|
586
|
+
mock_session_class.return_value = mock_session
|
|
587
|
+
|
|
588
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
589
|
+
result = integration._list_organizational_units()
|
|
590
|
+
|
|
591
|
+
assert len(result) == 1
|
|
592
|
+
assert result[0]["Id"] == "ou-1"
|
|
593
|
+
|
|
594
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
595
|
+
@patch(f"{PATH}.boto3.Session")
|
|
596
|
+
def test_list_organizational_units_error(self, mock_session_class, mock_mapper_class):
|
|
597
|
+
"""Test listing organizational units with error."""
|
|
598
|
+
mock_client = MagicMock()
|
|
599
|
+
error_response = {"Error": {"Code": "AccessDenied", "Message": "Access denied"}}
|
|
600
|
+
mock_client.list_roots.side_effect = ClientError(error_response, "ListRoots")
|
|
601
|
+
|
|
602
|
+
mock_session = MagicMock()
|
|
603
|
+
mock_session.client.return_value = mock_client
|
|
604
|
+
mock_session_class.return_value = mock_session
|
|
605
|
+
|
|
606
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
607
|
+
result = integration._list_organizational_units()
|
|
608
|
+
|
|
609
|
+
assert result == []
|
|
610
|
+
|
|
611
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
612
|
+
@patch(f"{PATH}.boto3.Session")
|
|
613
|
+
def test_list_service_control_policies_success(self, mock_session_class, mock_mapper_class):
|
|
614
|
+
"""Test listing service control policies successfully."""
|
|
615
|
+
mock_client = MagicMock()
|
|
616
|
+
mock_paginator = MagicMock()
|
|
617
|
+
mock_paginator.paginate.return_value = [
|
|
618
|
+
{
|
|
619
|
+
"Policies": [
|
|
620
|
+
{"Id": "p-1", "Name": "Policy1"},
|
|
621
|
+
{"Id": "p-2", "Name": "Policy2"},
|
|
622
|
+
]
|
|
623
|
+
}
|
|
624
|
+
]
|
|
625
|
+
mock_client.get_paginator.return_value = mock_paginator
|
|
626
|
+
mock_client.describe_policy.side_effect = [
|
|
627
|
+
{"Policy": {"Id": "p-1", "Name": "Policy1", "Content": "{}"}},
|
|
628
|
+
{"Policy": {"Id": "p-2", "Name": "Policy2", "Content": "{}"}},
|
|
629
|
+
]
|
|
630
|
+
|
|
631
|
+
mock_session = MagicMock()
|
|
632
|
+
mock_session.client.return_value = mock_client
|
|
633
|
+
mock_session_class.return_value = mock_session
|
|
634
|
+
|
|
635
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
636
|
+
result = integration._list_service_control_policies()
|
|
637
|
+
|
|
638
|
+
assert len(result) == 2
|
|
639
|
+
assert result[0]["Id"] == "p-1"
|
|
640
|
+
|
|
641
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
642
|
+
@patch(f"{PATH}.boto3.Session")
|
|
643
|
+
def test_list_service_control_policies_error(self, mock_session_class, mock_mapper_class):
|
|
644
|
+
"""Test listing service control policies with error."""
|
|
645
|
+
mock_client = MagicMock()
|
|
646
|
+
error_response = {"Error": {"Code": "AccessDenied", "Message": "Access denied"}}
|
|
647
|
+
mock_paginator = MagicMock()
|
|
648
|
+
mock_paginator.paginate.side_effect = ClientError(error_response, "ListPolicies")
|
|
649
|
+
mock_client.get_paginator.return_value = mock_paginator
|
|
650
|
+
|
|
651
|
+
mock_session = MagicMock()
|
|
652
|
+
mock_session.client.return_value = mock_client
|
|
653
|
+
mock_session_class.return_value = mock_session
|
|
654
|
+
|
|
655
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
656
|
+
result = integration._list_service_control_policies()
|
|
657
|
+
|
|
658
|
+
assert result == []
|
|
659
|
+
|
|
660
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
661
|
+
@patch(f"{PATH}.boto3.Session")
|
|
662
|
+
def test_fetch_fresh_org_data_success(self, mock_session_class, mock_mapper_class):
|
|
663
|
+
"""Test fetching fresh organization data successfully."""
|
|
664
|
+
mock_client = MagicMock()
|
|
665
|
+
mock_client.describe_organization.return_value = {
|
|
666
|
+
"Organization": {
|
|
667
|
+
"Id": "o-testorg",
|
|
668
|
+
"Arn": "arn:aws:organizations::123456789012:organization/o-testorg",
|
|
669
|
+
"MasterAccountId": "123456789012",
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
mock_paginator = MagicMock()
|
|
673
|
+
mock_paginator.paginate.return_value = [
|
|
674
|
+
{"Accounts": [{"Id": "111111111111", "Name": "Account1", "Status": "ACTIVE"}]}
|
|
675
|
+
]
|
|
676
|
+
mock_client.get_paginator.return_value = mock_paginator
|
|
677
|
+
|
|
678
|
+
mock_session = MagicMock()
|
|
679
|
+
mock_session.client.return_value = mock_client
|
|
680
|
+
mock_session_class.return_value = mock_session
|
|
681
|
+
|
|
682
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
683
|
+
integration._list_organizational_units = Mock(return_value=[{"Id": "ou-1"}])
|
|
684
|
+
integration._list_service_control_policies = Mock(return_value=[{"Id": "p-1"}])
|
|
685
|
+
|
|
686
|
+
result = integration._fetch_fresh_org_data()
|
|
687
|
+
|
|
688
|
+
assert "Id" in result
|
|
689
|
+
assert result["Id"] == "o-testorg"
|
|
690
|
+
assert "accounts" in result
|
|
691
|
+
assert len(result["accounts"]) == 1
|
|
692
|
+
assert "organizational_units" in result
|
|
693
|
+
assert "service_control_policies" in result
|
|
694
|
+
|
|
695
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
696
|
+
@patch(f"{PATH}.boto3.Session")
|
|
697
|
+
def test_fetch_fresh_org_data_error(self, mock_session_class, mock_mapper_class):
|
|
698
|
+
"""Test fetching fresh organization data with error."""
|
|
699
|
+
mock_client = MagicMock()
|
|
700
|
+
error_response = {"Error": {"Code": "AccessDenied", "Message": "Access denied"}}
|
|
701
|
+
mock_client.describe_organization.side_effect = ClientError(error_response, "DescribeOrganization")
|
|
702
|
+
|
|
703
|
+
mock_session = MagicMock()
|
|
704
|
+
mock_session.client.return_value = mock_client
|
|
705
|
+
mock_session_class.return_value = mock_session
|
|
706
|
+
|
|
707
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
708
|
+
result = integration._fetch_fresh_org_data()
|
|
709
|
+
|
|
710
|
+
assert result == {}
|
|
711
|
+
|
|
712
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
713
|
+
@patch(f"{PATH}.boto3.Session")
|
|
714
|
+
def test_fetch_compliance_data_from_cache(self, mock_session_class, mock_mapper_class):
|
|
715
|
+
"""Test fetching compliance data from cache."""
|
|
716
|
+
mock_session = MagicMock()
|
|
717
|
+
mock_session.client.return_value = MagicMock()
|
|
718
|
+
mock_session_class.return_value = mock_session
|
|
719
|
+
|
|
720
|
+
cached_data = {"Id": "o-testorg", "accounts": [], "organizational_units": [], "service_control_policies": []}
|
|
721
|
+
|
|
722
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
723
|
+
integration._is_cache_valid = Mock(return_value=True)
|
|
724
|
+
integration._load_cached_data = Mock(return_value=cached_data)
|
|
725
|
+
|
|
726
|
+
result = integration.fetch_compliance_data()
|
|
727
|
+
|
|
728
|
+
assert result == [cached_data]
|
|
729
|
+
assert integration.raw_org_data == cached_data
|
|
730
|
+
|
|
731
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
732
|
+
@patch(f"{PATH}.boto3.Session")
|
|
733
|
+
def test_fetch_compliance_data_force_refresh(self, mock_session_class, mock_mapper_class):
|
|
734
|
+
"""Test fetching compliance data with force refresh."""
|
|
735
|
+
mock_session = MagicMock()
|
|
736
|
+
mock_session.client.return_value = MagicMock()
|
|
737
|
+
mock_session_class.return_value = mock_session
|
|
738
|
+
|
|
739
|
+
fresh_data = {"Id": "o-freshorg", "accounts": [], "organizational_units": [], "service_control_policies": []}
|
|
740
|
+
|
|
741
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1", force_refresh=True)
|
|
742
|
+
integration._fetch_fresh_org_data = Mock(return_value=fresh_data)
|
|
743
|
+
integration._save_to_cache = Mock()
|
|
744
|
+
|
|
745
|
+
result = integration.fetch_compliance_data()
|
|
746
|
+
|
|
747
|
+
assert result == [fresh_data]
|
|
748
|
+
assert integration.raw_org_data == fresh_data
|
|
749
|
+
integration._save_to_cache.assert_called_once_with(fresh_data)
|
|
750
|
+
|
|
751
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
752
|
+
@patch(f"{PATH}.boto3.Session")
|
|
753
|
+
def test_create_compliance_item(self, mock_session_class, mock_mapper_class):
|
|
754
|
+
"""Test creating compliance item from raw data."""
|
|
755
|
+
mock_session = MagicMock()
|
|
756
|
+
mock_session.client.return_value = MagicMock()
|
|
757
|
+
mock_session_class.return_value = mock_session
|
|
758
|
+
|
|
759
|
+
mock_mapper = MagicMock()
|
|
760
|
+
mock_mapper.assess_organization_compliance.return_value = {"AC-1": "PASS"}
|
|
761
|
+
mock_mapper_class.return_value = mock_mapper
|
|
762
|
+
|
|
763
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
764
|
+
|
|
765
|
+
raw_data = {"Id": "o-testorg", "accounts": [], "organizational_units": [], "service_control_policies": []}
|
|
766
|
+
|
|
767
|
+
result = integration.create_compliance_item(raw_data)
|
|
768
|
+
|
|
769
|
+
assert isinstance(result, OrgComplianceItem)
|
|
770
|
+
assert result.org_data == raw_data
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
class TestEvidenceCollection:
|
|
774
|
+
"""Test cases for evidence collection methods."""
|
|
775
|
+
|
|
776
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
777
|
+
@patch(f"{PATH}.boto3.Session")
|
|
778
|
+
@patch(f"{PATH}.get_current_datetime")
|
|
779
|
+
def test_collect_org_evidence_as_attachments(self, mock_get_datetime, mock_session_class, mock_mapper_class):
|
|
780
|
+
"""Test collecting evidence as SSP attachments."""
|
|
781
|
+
mock_session = MagicMock()
|
|
782
|
+
mock_session.client.return_value = MagicMock()
|
|
783
|
+
mock_session_class.return_value = mock_session
|
|
784
|
+
|
|
785
|
+
mock_get_datetime.return_value = "2023-12-01"
|
|
786
|
+
|
|
787
|
+
integration = AWSOrganizationsEvidenceIntegration(
|
|
788
|
+
plan_id=123, region="us-east-1", collect_evidence=True, evidence_as_attachments=True
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
integration.raw_org_data = {
|
|
792
|
+
"Id": "o-testorg",
|
|
793
|
+
"accounts": [],
|
|
794
|
+
"organizational_units": [],
|
|
795
|
+
"service_control_policies": [],
|
|
796
|
+
}
|
|
797
|
+
integration._create_ssp_attachment = Mock()
|
|
798
|
+
|
|
799
|
+
integration._collect_org_evidence()
|
|
800
|
+
|
|
801
|
+
integration._create_ssp_attachment.assert_called_once_with("2023-12-01")
|
|
802
|
+
|
|
803
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
804
|
+
@patch(f"{PATH}.boto3.Session")
|
|
805
|
+
@patch(f"{PATH}.get_current_datetime")
|
|
806
|
+
def test_collect_org_evidence_as_records(self, mock_get_datetime, mock_session_class, mock_mapper_class):
|
|
807
|
+
"""Test collecting evidence as evidence records."""
|
|
808
|
+
mock_session = MagicMock()
|
|
809
|
+
mock_session.client.return_value = MagicMock()
|
|
810
|
+
mock_session_class.return_value = mock_session
|
|
811
|
+
|
|
812
|
+
mock_get_datetime.return_value = "2023-12-01"
|
|
813
|
+
|
|
814
|
+
integration = AWSOrganizationsEvidenceIntegration(
|
|
815
|
+
plan_id=123, region="us-east-1", collect_evidence=True, evidence_as_attachments=False
|
|
816
|
+
)
|
|
817
|
+
|
|
818
|
+
integration.raw_org_data = {
|
|
819
|
+
"Id": "o-testorg",
|
|
820
|
+
"accounts": [],
|
|
821
|
+
"organizational_units": [],
|
|
822
|
+
"service_control_policies": [],
|
|
823
|
+
}
|
|
824
|
+
integration._create_evidence_record = Mock()
|
|
825
|
+
|
|
826
|
+
integration._collect_org_evidence()
|
|
827
|
+
|
|
828
|
+
integration._create_evidence_record.assert_called_once_with("2023-12-01")
|
|
829
|
+
|
|
830
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
831
|
+
@patch(f"{PATH}.boto3.Session")
|
|
832
|
+
def test_collect_org_evidence_no_data(self, mock_session_class, mock_mapper_class):
|
|
833
|
+
"""Test collecting evidence when no data is available."""
|
|
834
|
+
mock_session = MagicMock()
|
|
835
|
+
mock_session.client.return_value = MagicMock()
|
|
836
|
+
mock_session_class.return_value = mock_session
|
|
837
|
+
|
|
838
|
+
integration = AWSOrganizationsEvidenceIntegration(
|
|
839
|
+
plan_id=123, region="us-east-1", collect_evidence=True, evidence_as_attachments=True
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
integration.raw_org_data = {}
|
|
843
|
+
|
|
844
|
+
integration._collect_org_evidence()
|
|
845
|
+
|
|
846
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
847
|
+
@patch(f"{PATH}.boto3.Session")
|
|
848
|
+
@patch(f"{PATH}.Api")
|
|
849
|
+
@patch(f"{PATH}.File")
|
|
850
|
+
def test_create_ssp_attachment_success(
|
|
851
|
+
self, mock_file_class, mock_api_class, mock_session_class, mock_mapper_class
|
|
852
|
+
):
|
|
853
|
+
"""Test creating SSP attachment successfully."""
|
|
854
|
+
mock_session = MagicMock()
|
|
855
|
+
mock_session.client.return_value = MagicMock()
|
|
856
|
+
mock_session_class.return_value = mock_session
|
|
857
|
+
|
|
858
|
+
mock_mapper = MagicMock()
|
|
859
|
+
mock_mapper.assess_organization_compliance.return_value = {"AC-1": "PASS", "PM-9": "PASS"}
|
|
860
|
+
mock_mapper_class.return_value = mock_mapper
|
|
861
|
+
|
|
862
|
+
mock_api = MagicMock()
|
|
863
|
+
mock_api_class.return_value = mock_api
|
|
864
|
+
|
|
865
|
+
mock_file_class.upload_file_to_regscale.return_value = True
|
|
866
|
+
|
|
867
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
868
|
+
integration.raw_org_data = {
|
|
869
|
+
"Id": "o-testorg",
|
|
870
|
+
"accounts": [],
|
|
871
|
+
"organizational_units": [],
|
|
872
|
+
"service_control_policies": [],
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
integration._create_ssp_attachment("2023-12-01")
|
|
876
|
+
|
|
877
|
+
mock_file_class.upload_file_to_regscale.assert_called_once()
|
|
878
|
+
call_args = mock_file_class.upload_file_to_regscale.call_args[1]
|
|
879
|
+
assert call_args["parent_id"] == 123
|
|
880
|
+
assert call_args["parent_module"] == "securityplans"
|
|
881
|
+
assert "org_evidence_" in call_args["file_name"]
|
|
882
|
+
assert "aws,organizations,governance,automated" == call_args["tags"]
|
|
883
|
+
|
|
884
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
885
|
+
@patch(f"{PATH}.boto3.Session")
|
|
886
|
+
@patch(f"{PATH}.Api")
|
|
887
|
+
@patch(f"{PATH}.File")
|
|
888
|
+
def test_create_ssp_attachment_failure(
|
|
889
|
+
self, mock_file_class, mock_api_class, mock_session_class, mock_mapper_class
|
|
890
|
+
):
|
|
891
|
+
"""Test creating SSP attachment with failure."""
|
|
892
|
+
mock_session = MagicMock()
|
|
893
|
+
mock_session.client.return_value = MagicMock()
|
|
894
|
+
mock_session_class.return_value = mock_session
|
|
895
|
+
|
|
896
|
+
mock_mapper = MagicMock()
|
|
897
|
+
mock_mapper.assess_organization_compliance.return_value = {}
|
|
898
|
+
mock_mapper_class.return_value = mock_mapper
|
|
899
|
+
|
|
900
|
+
mock_api = MagicMock()
|
|
901
|
+
mock_api_class.return_value = mock_api
|
|
902
|
+
|
|
903
|
+
mock_file_class.upload_file_to_regscale.return_value = False
|
|
904
|
+
|
|
905
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
906
|
+
integration.raw_org_data = {
|
|
907
|
+
"Id": "o-testorg",
|
|
908
|
+
"accounts": [],
|
|
909
|
+
"organizational_units": [],
|
|
910
|
+
"service_control_policies": [],
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
integration._create_ssp_attachment("2023-12-01")
|
|
914
|
+
|
|
915
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
916
|
+
@patch(f"{PATH}.boto3.Session")
|
|
917
|
+
@patch(f"{PATH}.Api")
|
|
918
|
+
@patch(f"{PATH}.File")
|
|
919
|
+
def test_create_ssp_attachment_exception(
|
|
920
|
+
self, mock_file_class, mock_api_class, mock_session_class, mock_mapper_class
|
|
921
|
+
):
|
|
922
|
+
"""Test creating SSP attachment with exception."""
|
|
923
|
+
mock_session = MagicMock()
|
|
924
|
+
mock_session.client.return_value = MagicMock()
|
|
925
|
+
mock_session_class.return_value = mock_session
|
|
926
|
+
|
|
927
|
+
mock_mapper = MagicMock()
|
|
928
|
+
mock_mapper.assess_organization_compliance.side_effect = Exception("Test error")
|
|
929
|
+
mock_mapper_class.return_value = mock_mapper
|
|
930
|
+
|
|
931
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
932
|
+
integration.raw_org_data = {
|
|
933
|
+
"Id": "o-testorg",
|
|
934
|
+
"accounts": [],
|
|
935
|
+
"organizational_units": [],
|
|
936
|
+
"service_control_policies": [],
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
integration._create_ssp_attachment("2023-12-01")
|
|
940
|
+
|
|
941
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
942
|
+
@patch(f"{PATH}.boto3.Session")
|
|
943
|
+
@patch(f"{PATH}.Evidence")
|
|
944
|
+
def test_create_evidence_record_success(self, mock_evidence_class, mock_session_class, mock_mapper_class):
|
|
945
|
+
"""Test creating evidence record successfully."""
|
|
946
|
+
mock_session = MagicMock()
|
|
947
|
+
mock_session.client.return_value = MagicMock()
|
|
948
|
+
mock_session_class.return_value = mock_session
|
|
949
|
+
|
|
950
|
+
mock_mapper = MagicMock()
|
|
951
|
+
mock_mapper.assess_organization_compliance.return_value = {"AC-1": "PASS", "PM-9": "FAIL"}
|
|
952
|
+
mock_mapper.get_control_description.side_effect = lambda x: f"Description for {x}"
|
|
953
|
+
mock_mapper_class.return_value = mock_mapper
|
|
954
|
+
|
|
955
|
+
mock_evidence = MagicMock()
|
|
956
|
+
mock_evidence.id = 999
|
|
957
|
+
mock_evidence_instance = MagicMock()
|
|
958
|
+
mock_evidence_instance.create.return_value = mock_evidence
|
|
959
|
+
mock_evidence_class.return_value = mock_evidence_instance
|
|
960
|
+
|
|
961
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1", evidence_frequency=90)
|
|
962
|
+
integration.raw_org_data = {
|
|
963
|
+
"Id": "o-testorg",
|
|
964
|
+
"accounts": [],
|
|
965
|
+
"organizational_units": [],
|
|
966
|
+
"service_control_policies": [],
|
|
967
|
+
}
|
|
968
|
+
integration._upload_evidence_file = Mock()
|
|
969
|
+
integration._link_evidence_to_ssp = Mock()
|
|
970
|
+
|
|
971
|
+
integration._create_evidence_record("2023-12-01")
|
|
972
|
+
|
|
973
|
+
mock_evidence_instance.create.assert_called_once()
|
|
974
|
+
integration._upload_evidence_file.assert_called_once_with(999, "2023-12-01")
|
|
975
|
+
integration._link_evidence_to_ssp.assert_called_once_with(999)
|
|
976
|
+
|
|
977
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
978
|
+
@patch(f"{PATH}.boto3.Session")
|
|
979
|
+
@patch(f"{PATH}.Evidence")
|
|
980
|
+
def test_create_evidence_record_creation_failure(self, mock_evidence_class, mock_session_class, mock_mapper_class):
|
|
981
|
+
"""Test creating evidence record when creation fails."""
|
|
982
|
+
mock_session = MagicMock()
|
|
983
|
+
mock_session.client.return_value = MagicMock()
|
|
984
|
+
mock_session_class.return_value = mock_session
|
|
985
|
+
|
|
986
|
+
mock_mapper = MagicMock()
|
|
987
|
+
mock_mapper.assess_organization_compliance.return_value = {}
|
|
988
|
+
mock_mapper_class.return_value = mock_mapper
|
|
989
|
+
|
|
990
|
+
mock_evidence_instance = MagicMock()
|
|
991
|
+
mock_evidence_instance.create.return_value = None
|
|
992
|
+
mock_evidence_class.return_value = mock_evidence_instance
|
|
993
|
+
|
|
994
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
995
|
+
integration.raw_org_data = {
|
|
996
|
+
"Id": "o-testorg",
|
|
997
|
+
"accounts": [],
|
|
998
|
+
"organizational_units": [],
|
|
999
|
+
"service_control_policies": [],
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
integration._create_evidence_record("2023-12-01")
|
|
1003
|
+
|
|
1004
|
+
mock_evidence_instance.create.assert_called_once()
|
|
1005
|
+
|
|
1006
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
1007
|
+
@patch(f"{PATH}.boto3.Session")
|
|
1008
|
+
@patch(f"{PATH}.Evidence")
|
|
1009
|
+
def test_create_evidence_record_exception(self, mock_evidence_class, mock_session_class, mock_mapper_class):
|
|
1010
|
+
"""Test creating evidence record with exception."""
|
|
1011
|
+
mock_session = MagicMock()
|
|
1012
|
+
mock_session.client.return_value = MagicMock()
|
|
1013
|
+
mock_session_class.return_value = mock_session
|
|
1014
|
+
|
|
1015
|
+
mock_mapper = MagicMock()
|
|
1016
|
+
mock_mapper.assess_organization_compliance.side_effect = Exception("Test error")
|
|
1017
|
+
mock_mapper_class.return_value = mock_mapper
|
|
1018
|
+
|
|
1019
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
1020
|
+
integration.raw_org_data = {
|
|
1021
|
+
"Id": "o-testorg",
|
|
1022
|
+
"accounts": [],
|
|
1023
|
+
"organizational_units": [],
|
|
1024
|
+
"service_control_policies": [],
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
integration._create_evidence_record("2023-12-01")
|
|
1028
|
+
|
|
1029
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
1030
|
+
@patch(f"{PATH}.boto3.Session")
|
|
1031
|
+
def test_build_evidence_description(self, mock_session_class, mock_mapper_class):
|
|
1032
|
+
"""Test building evidence description."""
|
|
1033
|
+
mock_session = MagicMock()
|
|
1034
|
+
mock_session.client.return_value = MagicMock()
|
|
1035
|
+
mock_session_class.return_value = mock_session
|
|
1036
|
+
|
|
1037
|
+
mock_mapper = MagicMock()
|
|
1038
|
+
mock_mapper.assess_organization_compliance.return_value = {"AC-1": "PASS", "PM-9": "FAIL", "AC-2": "PASS"}
|
|
1039
|
+
mock_mapper.get_control_description.side_effect = lambda x: f"{x} Description"
|
|
1040
|
+
mock_mapper_class.return_value = mock_mapper
|
|
1041
|
+
|
|
1042
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
1043
|
+
integration.raw_org_data = {
|
|
1044
|
+
"accounts": [{"Id": "111111111111"}],
|
|
1045
|
+
"organizational_units": [{"Id": "ou-1"}],
|
|
1046
|
+
"service_control_policies": [{"Id": "p-1"}],
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
result = integration._build_evidence_description("2023-12-01")
|
|
1050
|
+
|
|
1051
|
+
assert "AWS Organizations Governance Evidence" in result
|
|
1052
|
+
assert "2023-12-01" in result
|
|
1053
|
+
assert "AC-1" in result
|
|
1054
|
+
assert "PM-9" in result
|
|
1055
|
+
assert "AC-2" in result
|
|
1056
|
+
|
|
1057
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
1058
|
+
@patch(f"{PATH}.boto3.Session")
|
|
1059
|
+
@patch(f"{PATH}.Api")
|
|
1060
|
+
@patch(f"{PATH}.File")
|
|
1061
|
+
def test_upload_evidence_file_success(self, mock_file_class, mock_api_class, mock_session_class, mock_mapper_class):
|
|
1062
|
+
"""Test uploading evidence file successfully."""
|
|
1063
|
+
mock_session = MagicMock()
|
|
1064
|
+
mock_session.client.return_value = MagicMock()
|
|
1065
|
+
mock_session_class.return_value = mock_session
|
|
1066
|
+
|
|
1067
|
+
mock_mapper = MagicMock()
|
|
1068
|
+
mock_mapper.assess_organization_compliance.return_value = {"AC-1": "PASS"}
|
|
1069
|
+
mock_mapper_class.return_value = mock_mapper
|
|
1070
|
+
|
|
1071
|
+
mock_api = MagicMock()
|
|
1072
|
+
mock_api_class.return_value = mock_api
|
|
1073
|
+
|
|
1074
|
+
mock_file_class.upload_file_to_regscale.return_value = True
|
|
1075
|
+
|
|
1076
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
1077
|
+
integration.raw_org_data = {
|
|
1078
|
+
"Id": "o-testorg",
|
|
1079
|
+
"accounts": [],
|
|
1080
|
+
"organizational_units": [],
|
|
1081
|
+
"service_control_policies": [],
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
integration._upload_evidence_file(999, "2023-12-01")
|
|
1085
|
+
|
|
1086
|
+
mock_file_class.upload_file_to_regscale.assert_called_once()
|
|
1087
|
+
call_args = mock_file_class.upload_file_to_regscale.call_args[1]
|
|
1088
|
+
assert call_args["parent_id"] == 999
|
|
1089
|
+
assert call_args["parent_module"] == "evidence"
|
|
1090
|
+
assert "org_evidence_" in call_args["file_name"]
|
|
1091
|
+
assert "aws,organizations,governance" == call_args["tags"]
|
|
1092
|
+
|
|
1093
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
1094
|
+
@patch(f"{PATH}.boto3.Session")
|
|
1095
|
+
@patch(f"{PATH}.Api")
|
|
1096
|
+
@patch(f"{PATH}.File")
|
|
1097
|
+
def test_upload_evidence_file_failure(self, mock_file_class, mock_api_class, mock_session_class, mock_mapper_class):
|
|
1098
|
+
"""Test uploading evidence file with failure."""
|
|
1099
|
+
mock_session = MagicMock()
|
|
1100
|
+
mock_session.client.return_value = MagicMock()
|
|
1101
|
+
mock_session_class.return_value = mock_session
|
|
1102
|
+
|
|
1103
|
+
mock_mapper = MagicMock()
|
|
1104
|
+
mock_mapper.assess_organization_compliance.return_value = {}
|
|
1105
|
+
mock_mapper_class.return_value = mock_mapper
|
|
1106
|
+
|
|
1107
|
+
mock_api = MagicMock()
|
|
1108
|
+
mock_api_class.return_value = mock_api
|
|
1109
|
+
|
|
1110
|
+
mock_file_class.upload_file_to_regscale.return_value = False
|
|
1111
|
+
|
|
1112
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
1113
|
+
integration.raw_org_data = {
|
|
1114
|
+
"Id": "o-testorg",
|
|
1115
|
+
"accounts": [],
|
|
1116
|
+
"organizational_units": [],
|
|
1117
|
+
"service_control_policies": [],
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
integration._upload_evidence_file(999, "2023-12-01")
|
|
1121
|
+
|
|
1122
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
1123
|
+
@patch(f"{PATH}.boto3.Session")
|
|
1124
|
+
@patch(f"{PATH}.Api")
|
|
1125
|
+
@patch(f"{PATH}.File")
|
|
1126
|
+
def test_upload_evidence_file_exception(
|
|
1127
|
+
self, mock_file_class, mock_api_class, mock_session_class, mock_mapper_class
|
|
1128
|
+
):
|
|
1129
|
+
"""Test uploading evidence file with exception."""
|
|
1130
|
+
mock_session = MagicMock()
|
|
1131
|
+
mock_session.client.return_value = MagicMock()
|
|
1132
|
+
mock_session_class.return_value = mock_session
|
|
1133
|
+
|
|
1134
|
+
mock_mapper = MagicMock()
|
|
1135
|
+
mock_mapper.assess_organization_compliance.side_effect = Exception("Test error")
|
|
1136
|
+
mock_mapper_class.return_value = mock_mapper
|
|
1137
|
+
|
|
1138
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
1139
|
+
integration.raw_org_data = {
|
|
1140
|
+
"Id": "o-testorg",
|
|
1141
|
+
"accounts": [],
|
|
1142
|
+
"organizational_units": [],
|
|
1143
|
+
"service_control_policies": [],
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
integration._upload_evidence_file(999, "2023-12-01")
|
|
1147
|
+
|
|
1148
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
1149
|
+
@patch(f"{PATH}.boto3.Session")
|
|
1150
|
+
@patch(f"{PATH}.EvidenceMapping")
|
|
1151
|
+
def test_link_evidence_to_ssp_success(self, mock_mapping_class, mock_session_class, mock_mapper_class):
|
|
1152
|
+
"""Test linking evidence to SSP successfully."""
|
|
1153
|
+
mock_session = MagicMock()
|
|
1154
|
+
mock_session.client.return_value = MagicMock()
|
|
1155
|
+
mock_session_class.return_value = mock_session
|
|
1156
|
+
|
|
1157
|
+
mock_mapping = MagicMock()
|
|
1158
|
+
mock_mapping_class.return_value = mock_mapping
|
|
1159
|
+
|
|
1160
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
1161
|
+
|
|
1162
|
+
integration._link_evidence_to_ssp(999)
|
|
1163
|
+
|
|
1164
|
+
mock_mapping_class.assert_called_once_with(evidenceID=999, mappedID=123, mappingType="securityplans")
|
|
1165
|
+
mock_mapping.create.assert_called_once()
|
|
1166
|
+
|
|
1167
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
1168
|
+
@patch(f"{PATH}.boto3.Session")
|
|
1169
|
+
@patch(f"{PATH}.EvidenceMapping")
|
|
1170
|
+
def test_link_evidence_to_ssp_failure(self, mock_mapping_class, mock_session_class, mock_mapper_class):
|
|
1171
|
+
"""Test linking evidence to SSP with failure."""
|
|
1172
|
+
mock_session = MagicMock()
|
|
1173
|
+
mock_session.client.return_value = MagicMock()
|
|
1174
|
+
mock_session_class.return_value = mock_session
|
|
1175
|
+
|
|
1176
|
+
mock_mapping = MagicMock()
|
|
1177
|
+
mock_mapping.create.side_effect = Exception("Test error")
|
|
1178
|
+
mock_mapping_class.return_value = mock_mapping
|
|
1179
|
+
|
|
1180
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1")
|
|
1181
|
+
|
|
1182
|
+
integration._link_evidence_to_ssp(999)
|
|
1183
|
+
|
|
1184
|
+
|
|
1185
|
+
class TestSyncCompliance:
|
|
1186
|
+
"""Test cases for sync_compliance method."""
|
|
1187
|
+
|
|
1188
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
1189
|
+
@patch(f"{PATH}.boto3.Session")
|
|
1190
|
+
def test_sync_compliance_with_evidence_collection(self, mock_session_class, mock_mapper_class):
|
|
1191
|
+
"""Test sync_compliance with evidence collection enabled."""
|
|
1192
|
+
mock_session = MagicMock()
|
|
1193
|
+
mock_session.client.return_value = MagicMock()
|
|
1194
|
+
mock_session_class.return_value = mock_session
|
|
1195
|
+
|
|
1196
|
+
mock_mapper = MagicMock()
|
|
1197
|
+
mock_mapper.assess_organization_compliance.return_value = {"AC-1": "PASS"}
|
|
1198
|
+
mock_mapper_class.return_value = mock_mapper
|
|
1199
|
+
|
|
1200
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1", collect_evidence=True)
|
|
1201
|
+
integration.fetch_compliance_data = Mock(
|
|
1202
|
+
return_value=[
|
|
1203
|
+
{"Id": "o-testorg", "accounts": [], "organizational_units": [], "service_control_policies": []}
|
|
1204
|
+
]
|
|
1205
|
+
)
|
|
1206
|
+
integration._collect_org_evidence = Mock()
|
|
1207
|
+
|
|
1208
|
+
with patch.object(integration.__class__.__bases__[0], "sync_compliance"):
|
|
1209
|
+
integration.sync_compliance()
|
|
1210
|
+
|
|
1211
|
+
integration._collect_org_evidence.assert_called_once()
|
|
1212
|
+
|
|
1213
|
+
@patch(f"{PATH}.OrgControlMapper")
|
|
1214
|
+
@patch(f"{PATH}.boto3.Session")
|
|
1215
|
+
def test_sync_compliance_without_evidence_collection(self, mock_session_class, mock_mapper_class):
|
|
1216
|
+
"""Test sync_compliance without evidence collection."""
|
|
1217
|
+
mock_session = MagicMock()
|
|
1218
|
+
mock_session.client.return_value = MagicMock()
|
|
1219
|
+
mock_session_class.return_value = mock_session
|
|
1220
|
+
|
|
1221
|
+
mock_mapper = MagicMock()
|
|
1222
|
+
mock_mapper.assess_organization_compliance.return_value = {"AC-1": "PASS"}
|
|
1223
|
+
mock_mapper_class.return_value = mock_mapper
|
|
1224
|
+
|
|
1225
|
+
integration = AWSOrganizationsEvidenceIntegration(plan_id=123, region="us-east-1", collect_evidence=False)
|
|
1226
|
+
integration.fetch_compliance_data = Mock(
|
|
1227
|
+
return_value=[
|
|
1228
|
+
{"Id": "o-testorg", "accounts": [], "organizational_units": [], "service_control_policies": []}
|
|
1229
|
+
]
|
|
1230
|
+
)
|
|
1231
|
+
integration._collect_org_evidence = Mock()
|
|
1232
|
+
|
|
1233
|
+
with patch.object(integration.__class__.__bases__[0], "sync_compliance"):
|
|
1234
|
+
integration.sync_compliance()
|
|
1235
|
+
|
|
1236
|
+
integration._collect_org_evidence.assert_not_called()
|
|
1237
|
+
|
|
1238
|
+
|
|
1239
|
+
if __name__ == "__main__":
|
|
1240
|
+
pytest.main([__file__, "-v"])
|