regscale-cli 6.27.3.0__py3-none-any.whl → 6.28.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- 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/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 +34 -4
- 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.0.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/RECORD +112 -33
- 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.0.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.27.3.0.dist-info → regscale_cli-6.28.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Unit tests for AWS S3 Control Mappings."""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from regscale.integrations.commercial.aws.s3_control_mappings import (
|
|
8
|
+
S3_CONTROL_MAPPINGS,
|
|
9
|
+
S3ControlMapper,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestS3ControlMappings:
|
|
14
|
+
"""Test S3 control mappings constants."""
|
|
15
|
+
|
|
16
|
+
def test_s3_control_mappings_exist(self):
|
|
17
|
+
"""Test that S3 control mappings dictionary exists and has content."""
|
|
18
|
+
assert len(S3_CONTROL_MAPPINGS) > 0
|
|
19
|
+
assert "SC-13" in S3_CONTROL_MAPPINGS
|
|
20
|
+
assert "SC-28" in S3_CONTROL_MAPPINGS
|
|
21
|
+
assert "AC-3" in S3_CONTROL_MAPPINGS
|
|
22
|
+
assert "AC-6" in S3_CONTROL_MAPPINGS
|
|
23
|
+
assert "AU-2" in S3_CONTROL_MAPPINGS
|
|
24
|
+
assert "AU-9" in S3_CONTROL_MAPPINGS
|
|
25
|
+
assert "CP-9" in S3_CONTROL_MAPPINGS
|
|
26
|
+
|
|
27
|
+
def test_sc13_mapping_structure(self):
|
|
28
|
+
"""Test SC-13 mapping structure."""
|
|
29
|
+
sc13 = S3_CONTROL_MAPPINGS["SC-13"]
|
|
30
|
+
assert "name" in sc13
|
|
31
|
+
assert "description" in sc13
|
|
32
|
+
assert "checks" in sc13
|
|
33
|
+
assert "encryption_at_rest" in sc13["checks"]
|
|
34
|
+
assert "encryption_algorithm" in sc13["checks"]
|
|
35
|
+
|
|
36
|
+
def test_sc28_mapping_structure(self):
|
|
37
|
+
"""Test SC-28 mapping structure."""
|
|
38
|
+
sc28 = S3_CONTROL_MAPPINGS["SC-28"]
|
|
39
|
+
assert "name" in sc28
|
|
40
|
+
assert "description" in sc28
|
|
41
|
+
assert "checks" in sc28
|
|
42
|
+
assert "bucket_encryption" in sc28["checks"]
|
|
43
|
+
assert "versioning_enabled" in sc28["checks"]
|
|
44
|
+
|
|
45
|
+
def test_ac3_mapping_structure(self):
|
|
46
|
+
"""Test AC-3 mapping structure."""
|
|
47
|
+
ac3 = S3_CONTROL_MAPPINGS["AC-3"]
|
|
48
|
+
assert "name" in ac3
|
|
49
|
+
assert "description" in ac3
|
|
50
|
+
assert "checks" in ac3
|
|
51
|
+
assert "public_access_blocked" in ac3["checks"]
|
|
52
|
+
assert "bucket_policy" in ac3["checks"]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TestS3ControlMapperInitialization:
|
|
56
|
+
"""Test S3ControlMapper initialization."""
|
|
57
|
+
|
|
58
|
+
def test_init_with_nist_framework(self):
|
|
59
|
+
"""Test initialization with NIST framework."""
|
|
60
|
+
mapper = S3ControlMapper(framework="NIST800-53R5")
|
|
61
|
+
assert mapper.framework == "NIST800-53R5"
|
|
62
|
+
assert mapper.mappings == S3_CONTROL_MAPPINGS
|
|
63
|
+
|
|
64
|
+
def test_init_default_framework(self):
|
|
65
|
+
"""Test initialization with default framework."""
|
|
66
|
+
mapper = S3ControlMapper()
|
|
67
|
+
assert mapper.framework == "NIST800-53R5"
|
|
68
|
+
assert mapper.mappings == S3_CONTROL_MAPPINGS
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class TestAssessBucketCompliance:
|
|
72
|
+
"""Test assess_bucket_compliance method."""
|
|
73
|
+
|
|
74
|
+
def test_assess_compliant_bucket(self):
|
|
75
|
+
"""Test assessing a fully compliant bucket."""
|
|
76
|
+
mapper = S3ControlMapper()
|
|
77
|
+
bucket_data = {
|
|
78
|
+
"Name": "compliant-bucket",
|
|
79
|
+
"Encryption": {"Enabled": True, "Algorithm": "AES256"},
|
|
80
|
+
"Versioning": {"Status": "Enabled"},
|
|
81
|
+
"PublicAccessBlock": {
|
|
82
|
+
"BlockPublicAcls": True,
|
|
83
|
+
"IgnorePublicAcls": True,
|
|
84
|
+
"BlockPublicPolicy": True,
|
|
85
|
+
"RestrictPublicBuckets": True,
|
|
86
|
+
},
|
|
87
|
+
"PolicyStatus": {"IsPublic": False},
|
|
88
|
+
"ACL": {"GrantCount": 2},
|
|
89
|
+
"Logging": {"Enabled": True, "TargetBucket": "logs-bucket"},
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
results = mapper.assess_bucket_compliance(bucket_data)
|
|
93
|
+
|
|
94
|
+
assert results["SC-13"] == "PASS"
|
|
95
|
+
assert results["SC-28"] == "PASS"
|
|
96
|
+
assert results["AC-3"] == "PASS"
|
|
97
|
+
assert results["AC-6"] == "PASS"
|
|
98
|
+
assert results["AU-2"] == "PASS"
|
|
99
|
+
assert results["AU-9"] == "PASS"
|
|
100
|
+
assert results["CP-9"] == "PASS"
|
|
101
|
+
|
|
102
|
+
def test_assess_bucket_without_encryption(self):
|
|
103
|
+
"""Test assessing bucket without encryption."""
|
|
104
|
+
mapper = S3ControlMapper()
|
|
105
|
+
bucket_data = {
|
|
106
|
+
"Name": "unencrypted-bucket",
|
|
107
|
+
"Encryption": {"Enabled": False},
|
|
108
|
+
"Versioning": {"Status": "Enabled"},
|
|
109
|
+
"PublicAccessBlock": {
|
|
110
|
+
"BlockPublicAcls": True,
|
|
111
|
+
"IgnorePublicAcls": True,
|
|
112
|
+
"BlockPublicPolicy": True,
|
|
113
|
+
"RestrictPublicBuckets": True,
|
|
114
|
+
},
|
|
115
|
+
"PolicyStatus": {"IsPublic": False},
|
|
116
|
+
"ACL": {"GrantCount": 2},
|
|
117
|
+
"Logging": {"Enabled": True},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
results = mapper.assess_bucket_compliance(bucket_data)
|
|
121
|
+
|
|
122
|
+
assert results["SC-13"] == "FAIL"
|
|
123
|
+
assert results["SC-28"] == "FAIL"
|
|
124
|
+
|
|
125
|
+
def test_assess_bucket_with_public_access(self):
|
|
126
|
+
"""Test assessing bucket with public access."""
|
|
127
|
+
mapper = S3ControlMapper()
|
|
128
|
+
bucket_data = {
|
|
129
|
+
"Name": "public-bucket",
|
|
130
|
+
"Encryption": {"Enabled": True, "Algorithm": "AES256"},
|
|
131
|
+
"Versioning": {"Status": "Enabled"},
|
|
132
|
+
"PublicAccessBlock": {
|
|
133
|
+
"BlockPublicAcls": False,
|
|
134
|
+
"IgnorePublicAcls": False,
|
|
135
|
+
"BlockPublicPolicy": False,
|
|
136
|
+
"RestrictPublicBuckets": False,
|
|
137
|
+
},
|
|
138
|
+
"PolicyStatus": {"IsPublic": True},
|
|
139
|
+
"ACL": {"GrantCount": 2},
|
|
140
|
+
"Logging": {"Enabled": False},
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
results = mapper.assess_bucket_compliance(bucket_data)
|
|
144
|
+
|
|
145
|
+
assert results["AC-3"] == "FAIL"
|
|
146
|
+
assert results["AC-6"] == "FAIL"
|
|
147
|
+
assert results["AU-2"] == "FAIL"
|
|
148
|
+
|
|
149
|
+
def test_assess_bucket_without_versioning(self):
|
|
150
|
+
"""Test assessing bucket without versioning."""
|
|
151
|
+
mapper = S3ControlMapper()
|
|
152
|
+
bucket_data = {
|
|
153
|
+
"Name": "no-versioning-bucket",
|
|
154
|
+
"Encryption": {"Enabled": True, "Algorithm": "AES256"},
|
|
155
|
+
"Versioning": {"Status": "Disabled"},
|
|
156
|
+
"PublicAccessBlock": {
|
|
157
|
+
"BlockPublicAcls": True,
|
|
158
|
+
"IgnorePublicAcls": True,
|
|
159
|
+
"BlockPublicPolicy": True,
|
|
160
|
+
"RestrictPublicBuckets": True,
|
|
161
|
+
},
|
|
162
|
+
"PolicyStatus": {"IsPublic": False},
|
|
163
|
+
"ACL": {"GrantCount": 2},
|
|
164
|
+
"Logging": {"Enabled": True},
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
results = mapper.assess_bucket_compliance(bucket_data)
|
|
168
|
+
|
|
169
|
+
assert results["SC-28"] == "FAIL"
|
|
170
|
+
assert results["CP-9"] == "FAIL"
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class TestAssessAllBucketsCompliance:
|
|
174
|
+
"""Test assess_all_buckets_compliance method."""
|
|
175
|
+
|
|
176
|
+
def test_assess_all_compliant_buckets(self):
|
|
177
|
+
"""Test assessing all compliant buckets."""
|
|
178
|
+
mapper = S3ControlMapper()
|
|
179
|
+
buckets = [
|
|
180
|
+
{
|
|
181
|
+
"Name": "bucket1",
|
|
182
|
+
"Encryption": {"Enabled": True, "Algorithm": "AES256"},
|
|
183
|
+
"Versioning": {"Status": "Enabled"},
|
|
184
|
+
"PublicAccessBlock": {
|
|
185
|
+
"BlockPublicAcls": True,
|
|
186
|
+
"IgnorePublicAcls": True,
|
|
187
|
+
"BlockPublicPolicy": True,
|
|
188
|
+
"RestrictPublicBuckets": True,
|
|
189
|
+
},
|
|
190
|
+
"PolicyStatus": {"IsPublic": False},
|
|
191
|
+
"ACL": {"GrantCount": 2},
|
|
192
|
+
"Logging": {"Enabled": True},
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"Name": "bucket2",
|
|
196
|
+
"Encryption": {"Enabled": True, "Algorithm": "aws:kms"},
|
|
197
|
+
"Versioning": {"Status": "Enabled"},
|
|
198
|
+
"PublicAccessBlock": {
|
|
199
|
+
"BlockPublicAcls": True,
|
|
200
|
+
"IgnorePublicAcls": True,
|
|
201
|
+
"BlockPublicPolicy": True,
|
|
202
|
+
"RestrictPublicBuckets": True,
|
|
203
|
+
},
|
|
204
|
+
"PolicyStatus": {"IsPublic": False},
|
|
205
|
+
"ACL": {"GrantCount": 1},
|
|
206
|
+
"Logging": {"Enabled": True},
|
|
207
|
+
},
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
results = mapper.assess_all_buckets_compliance(buckets)
|
|
211
|
+
|
|
212
|
+
assert all(result == "PASS" for result in results.values())
|
|
213
|
+
|
|
214
|
+
def test_assess_buckets_with_one_non_compliant(self):
|
|
215
|
+
"""Test assessing buckets where one is non-compliant."""
|
|
216
|
+
mapper = S3ControlMapper()
|
|
217
|
+
buckets = [
|
|
218
|
+
{
|
|
219
|
+
"Name": "bucket1",
|
|
220
|
+
"Encryption": {"Enabled": True, "Algorithm": "AES256"},
|
|
221
|
+
"Versioning": {"Status": "Enabled"},
|
|
222
|
+
"PublicAccessBlock": {
|
|
223
|
+
"BlockPublicAcls": True,
|
|
224
|
+
"IgnorePublicAcls": True,
|
|
225
|
+
"BlockPublicPolicy": True,
|
|
226
|
+
"RestrictPublicBuckets": True,
|
|
227
|
+
},
|
|
228
|
+
"PolicyStatus": {"IsPublic": False},
|
|
229
|
+
"ACL": {"GrantCount": 2},
|
|
230
|
+
"Logging": {"Enabled": True},
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
"Name": "bucket2",
|
|
234
|
+
"Encryption": {"Enabled": False},
|
|
235
|
+
"Versioning": {"Status": "Disabled"},
|
|
236
|
+
"PublicAccessBlock": {
|
|
237
|
+
"BlockPublicAcls": False,
|
|
238
|
+
"IgnorePublicAcls": False,
|
|
239
|
+
"BlockPublicPolicy": False,
|
|
240
|
+
"RestrictPublicBuckets": False,
|
|
241
|
+
},
|
|
242
|
+
"PolicyStatus": {"IsPublic": True},
|
|
243
|
+
"ACL": {"GrantCount": 10},
|
|
244
|
+
"Logging": {"Enabled": False},
|
|
245
|
+
},
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
results = mapper.assess_all_buckets_compliance(buckets)
|
|
249
|
+
|
|
250
|
+
# All controls should fail because one bucket fails each control
|
|
251
|
+
assert results["SC-13"] == "FAIL"
|
|
252
|
+
assert results["SC-28"] == "FAIL"
|
|
253
|
+
assert results["AC-3"] == "FAIL"
|
|
254
|
+
assert results["AC-6"] == "FAIL"
|
|
255
|
+
assert results["AU-2"] == "FAIL"
|
|
256
|
+
assert results["CP-9"] == "FAIL"
|
|
257
|
+
|
|
258
|
+
def test_assess_empty_bucket_list(self):
|
|
259
|
+
"""Test assessing empty bucket list."""
|
|
260
|
+
mapper = S3ControlMapper()
|
|
261
|
+
results = mapper.assess_all_buckets_compliance([])
|
|
262
|
+
|
|
263
|
+
# Empty list should pass all controls
|
|
264
|
+
assert all(result == "PASS" for result in results.values())
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class TestAssessSC13:
|
|
268
|
+
"""Test _assess_sc13 method."""
|
|
269
|
+
|
|
270
|
+
def test_bucket_with_aes256_encryption_passes(self):
|
|
271
|
+
"""Test bucket with AES256 encryption passes."""
|
|
272
|
+
mapper = S3ControlMapper()
|
|
273
|
+
bucket_data = {"Name": "test-bucket", "Encryption": {"Enabled": True, "Algorithm": "AES256"}}
|
|
274
|
+
|
|
275
|
+
result = mapper._assess_sc13(bucket_data)
|
|
276
|
+
assert result == "PASS"
|
|
277
|
+
|
|
278
|
+
def test_bucket_with_kms_encryption_passes(self):
|
|
279
|
+
"""Test bucket with KMS encryption passes."""
|
|
280
|
+
mapper = S3ControlMapper()
|
|
281
|
+
bucket_data = {"Name": "test-bucket", "Encryption": {"Enabled": True, "Algorithm": "aws:kms"}}
|
|
282
|
+
|
|
283
|
+
result = mapper._assess_sc13(bucket_data)
|
|
284
|
+
assert result == "PASS"
|
|
285
|
+
|
|
286
|
+
def test_bucket_without_encryption_fails(self):
|
|
287
|
+
"""Test bucket without encryption fails."""
|
|
288
|
+
mapper = S3ControlMapper()
|
|
289
|
+
bucket_data = {"Name": "test-bucket", "Encryption": {"Enabled": False}}
|
|
290
|
+
|
|
291
|
+
result = mapper._assess_sc13(bucket_data)
|
|
292
|
+
assert result == "FAIL"
|
|
293
|
+
|
|
294
|
+
def test_bucket_with_weak_algorithm_fails(self):
|
|
295
|
+
"""Test bucket with weak encryption algorithm fails."""
|
|
296
|
+
mapper = S3ControlMapper()
|
|
297
|
+
bucket_data = {"Name": "test-bucket", "Encryption": {"Enabled": True, "Algorithm": "WEAK_ALGO"}}
|
|
298
|
+
|
|
299
|
+
result = mapper._assess_sc13(bucket_data)
|
|
300
|
+
assert result == "FAIL"
|
|
301
|
+
|
|
302
|
+
def test_bucket_with_missing_encryption_key_fails(self):
|
|
303
|
+
"""Test bucket with missing Encryption key fails."""
|
|
304
|
+
mapper = S3ControlMapper()
|
|
305
|
+
bucket_data = {"Name": "test-bucket"}
|
|
306
|
+
|
|
307
|
+
result = mapper._assess_sc13(bucket_data)
|
|
308
|
+
assert result == "FAIL"
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class TestAssessSC28:
|
|
312
|
+
"""Test _assess_sc28 method."""
|
|
313
|
+
|
|
314
|
+
def test_bucket_with_encryption_and_versioning_passes(self):
|
|
315
|
+
"""Test bucket with encryption and versioning passes."""
|
|
316
|
+
mapper = S3ControlMapper()
|
|
317
|
+
bucket_data = {
|
|
318
|
+
"Name": "test-bucket",
|
|
319
|
+
"Encryption": {"Enabled": True, "Algorithm": "AES256"},
|
|
320
|
+
"Versioning": {"Status": "Enabled"},
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
result = mapper._assess_sc28(bucket_data)
|
|
324
|
+
assert result == "PASS"
|
|
325
|
+
|
|
326
|
+
def test_bucket_without_encryption_fails(self):
|
|
327
|
+
"""Test bucket without encryption fails."""
|
|
328
|
+
mapper = S3ControlMapper()
|
|
329
|
+
bucket_data = {
|
|
330
|
+
"Name": "test-bucket",
|
|
331
|
+
"Encryption": {"Enabled": False},
|
|
332
|
+
"Versioning": {"Status": "Enabled"},
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
result = mapper._assess_sc28(bucket_data)
|
|
336
|
+
assert result == "FAIL"
|
|
337
|
+
|
|
338
|
+
def test_bucket_without_versioning_fails(self):
|
|
339
|
+
"""Test bucket without versioning fails."""
|
|
340
|
+
mapper = S3ControlMapper()
|
|
341
|
+
bucket_data = {
|
|
342
|
+
"Name": "test-bucket",
|
|
343
|
+
"Encryption": {"Enabled": True, "Algorithm": "AES256"},
|
|
344
|
+
"Versioning": {"Status": "Disabled"},
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
result = mapper._assess_sc28(bucket_data)
|
|
348
|
+
assert result == "FAIL"
|
|
349
|
+
|
|
350
|
+
def test_bucket_with_suspended_versioning_fails(self):
|
|
351
|
+
"""Test bucket with suspended versioning fails."""
|
|
352
|
+
mapper = S3ControlMapper()
|
|
353
|
+
bucket_data = {
|
|
354
|
+
"Name": "test-bucket",
|
|
355
|
+
"Encryption": {"Enabled": True, "Algorithm": "AES256"},
|
|
356
|
+
"Versioning": {"Status": "Suspended"},
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
result = mapper._assess_sc28(bucket_data)
|
|
360
|
+
assert result == "FAIL"
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
class TestAssessAC3:
|
|
364
|
+
"""Test _assess_ac3 method."""
|
|
365
|
+
|
|
366
|
+
def test_bucket_with_all_public_access_blocks_passes(self):
|
|
367
|
+
"""Test bucket with all public access blocks enabled passes."""
|
|
368
|
+
mapper = S3ControlMapper()
|
|
369
|
+
bucket_data = {
|
|
370
|
+
"Name": "test-bucket",
|
|
371
|
+
"PublicAccessBlock": {
|
|
372
|
+
"BlockPublicAcls": True,
|
|
373
|
+
"IgnorePublicAcls": True,
|
|
374
|
+
"BlockPublicPolicy": True,
|
|
375
|
+
"RestrictPublicBuckets": True,
|
|
376
|
+
},
|
|
377
|
+
"PolicyStatus": {"IsPublic": False},
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
result = mapper._assess_ac3(bucket_data)
|
|
381
|
+
assert result == "PASS"
|
|
382
|
+
|
|
383
|
+
def test_bucket_without_block_public_acls_fails(self):
|
|
384
|
+
"""Test bucket without BlockPublicAcls fails."""
|
|
385
|
+
mapper = S3ControlMapper()
|
|
386
|
+
bucket_data = {
|
|
387
|
+
"Name": "test-bucket",
|
|
388
|
+
"PublicAccessBlock": {
|
|
389
|
+
"BlockPublicAcls": False,
|
|
390
|
+
"IgnorePublicAcls": True,
|
|
391
|
+
"BlockPublicPolicy": True,
|
|
392
|
+
"RestrictPublicBuckets": True,
|
|
393
|
+
},
|
|
394
|
+
"PolicyStatus": {"IsPublic": False},
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
result = mapper._assess_ac3(bucket_data)
|
|
398
|
+
assert result == "FAIL"
|
|
399
|
+
|
|
400
|
+
def test_bucket_without_ignore_public_acls_fails(self):
|
|
401
|
+
"""Test bucket without IgnorePublicAcls fails."""
|
|
402
|
+
mapper = S3ControlMapper()
|
|
403
|
+
bucket_data = {
|
|
404
|
+
"Name": "test-bucket",
|
|
405
|
+
"PublicAccessBlock": {
|
|
406
|
+
"BlockPublicAcls": True,
|
|
407
|
+
"IgnorePublicAcls": False,
|
|
408
|
+
"BlockPublicPolicy": True,
|
|
409
|
+
"RestrictPublicBuckets": True,
|
|
410
|
+
},
|
|
411
|
+
"PolicyStatus": {"IsPublic": False},
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
result = mapper._assess_ac3(bucket_data)
|
|
415
|
+
assert result == "FAIL"
|
|
416
|
+
|
|
417
|
+
def test_bucket_with_public_policy_fails(self):
|
|
418
|
+
"""Test bucket with public policy fails."""
|
|
419
|
+
mapper = S3ControlMapper()
|
|
420
|
+
bucket_data = {
|
|
421
|
+
"Name": "test-bucket",
|
|
422
|
+
"PublicAccessBlock": {
|
|
423
|
+
"BlockPublicAcls": True,
|
|
424
|
+
"IgnorePublicAcls": True,
|
|
425
|
+
"BlockPublicPolicy": True,
|
|
426
|
+
"RestrictPublicBuckets": True,
|
|
427
|
+
},
|
|
428
|
+
"PolicyStatus": {"IsPublic": True},
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
result = mapper._assess_ac3(bucket_data)
|
|
432
|
+
assert result == "FAIL"
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
class TestAssessAC6:
|
|
436
|
+
"""Test _assess_ac6 method."""
|
|
437
|
+
|
|
438
|
+
def test_bucket_with_minimal_acl_grants_passes(self):
|
|
439
|
+
"""Test bucket with minimal ACL grants passes."""
|
|
440
|
+
mapper = S3ControlMapper()
|
|
441
|
+
bucket_data = {
|
|
442
|
+
"Name": "test-bucket",
|
|
443
|
+
"ACL": {"GrantCount": 2},
|
|
444
|
+
"PolicyStatus": {"IsPublic": False},
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
result = mapper._assess_ac6(bucket_data)
|
|
448
|
+
assert result == "PASS"
|
|
449
|
+
|
|
450
|
+
def test_bucket_with_excessive_acl_grants_fails(self):
|
|
451
|
+
"""Test bucket with excessive ACL grants fails."""
|
|
452
|
+
mapper = S3ControlMapper()
|
|
453
|
+
bucket_data = {
|
|
454
|
+
"Name": "test-bucket",
|
|
455
|
+
"ACL": {"GrantCount": 10},
|
|
456
|
+
"PolicyStatus": {"IsPublic": False},
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
result = mapper._assess_ac6(bucket_data)
|
|
460
|
+
assert result == "FAIL"
|
|
461
|
+
|
|
462
|
+
def test_bucket_with_public_policy_fails(self):
|
|
463
|
+
"""Test bucket with public policy fails AC-6."""
|
|
464
|
+
mapper = S3ControlMapper()
|
|
465
|
+
bucket_data = {
|
|
466
|
+
"Name": "test-bucket",
|
|
467
|
+
"ACL": {"GrantCount": 2},
|
|
468
|
+
"PolicyStatus": {"IsPublic": True},
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
result = mapper._assess_ac6(bucket_data)
|
|
472
|
+
assert result == "FAIL"
|
|
473
|
+
|
|
474
|
+
def test_bucket_with_zero_acl_grants_passes(self):
|
|
475
|
+
"""Test bucket with zero ACL grants passes."""
|
|
476
|
+
mapper = S3ControlMapper()
|
|
477
|
+
bucket_data = {
|
|
478
|
+
"Name": "test-bucket",
|
|
479
|
+
"ACL": {"GrantCount": 0},
|
|
480
|
+
"PolicyStatus": {"IsPublic": False},
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
result = mapper._assess_ac6(bucket_data)
|
|
484
|
+
assert result == "PASS"
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
class TestAssessAU2:
|
|
488
|
+
"""Test _assess_au2 method."""
|
|
489
|
+
|
|
490
|
+
def test_bucket_with_logging_enabled_passes(self):
|
|
491
|
+
"""Test bucket with access logging enabled passes."""
|
|
492
|
+
mapper = S3ControlMapper()
|
|
493
|
+
bucket_data = {"Name": "test-bucket", "Logging": {"Enabled": True, "TargetBucket": "logs-bucket"}}
|
|
494
|
+
|
|
495
|
+
result = mapper._assess_au2(bucket_data)
|
|
496
|
+
assert result == "PASS"
|
|
497
|
+
|
|
498
|
+
def test_bucket_without_logging_fails(self):
|
|
499
|
+
"""Test bucket without access logging fails."""
|
|
500
|
+
mapper = S3ControlMapper()
|
|
501
|
+
bucket_data = {"Name": "test-bucket", "Logging": {"Enabled": False}}
|
|
502
|
+
|
|
503
|
+
result = mapper._assess_au2(bucket_data)
|
|
504
|
+
assert result == "FAIL"
|
|
505
|
+
|
|
506
|
+
def test_bucket_with_missing_logging_key_fails(self):
|
|
507
|
+
"""Test bucket with missing Logging key fails."""
|
|
508
|
+
mapper = S3ControlMapper()
|
|
509
|
+
bucket_data = {"Name": "test-bucket"}
|
|
510
|
+
|
|
511
|
+
result = mapper._assess_au2(bucket_data)
|
|
512
|
+
assert result == "FAIL"
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
class TestAssessAU9:
|
|
516
|
+
"""Test _assess_au9 method."""
|
|
517
|
+
|
|
518
|
+
def test_bucket_with_logging_configuration_passes(self):
|
|
519
|
+
"""Test bucket with logging configuration passes."""
|
|
520
|
+
mapper = S3ControlMapper()
|
|
521
|
+
bucket_data = {"Name": "test-bucket", "Logging": {"Enabled": True, "TargetBucket": "logs-bucket"}}
|
|
522
|
+
|
|
523
|
+
result = mapper._assess_au9(bucket_data)
|
|
524
|
+
assert result == "PASS"
|
|
525
|
+
|
|
526
|
+
def test_bucket_without_logging_passes(self):
|
|
527
|
+
"""Test bucket without logging passes (N/A case)."""
|
|
528
|
+
mapper = S3ControlMapper()
|
|
529
|
+
bucket_data = {"Name": "test-bucket", "Logging": {"Enabled": False}}
|
|
530
|
+
|
|
531
|
+
result = mapper._assess_au9(bucket_data)
|
|
532
|
+
assert result == "PASS"
|
|
533
|
+
|
|
534
|
+
def test_bucket_with_empty_logging_passes(self):
|
|
535
|
+
"""Test bucket with empty logging configuration passes."""
|
|
536
|
+
mapper = S3ControlMapper()
|
|
537
|
+
bucket_data = {"Name": "test-bucket", "Logging": {}}
|
|
538
|
+
|
|
539
|
+
result = mapper._assess_au9(bucket_data)
|
|
540
|
+
assert result == "PASS"
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
class TestAssessCP9:
|
|
544
|
+
"""Test _assess_cp9 method."""
|
|
545
|
+
|
|
546
|
+
def test_bucket_with_versioning_enabled_passes(self):
|
|
547
|
+
"""Test bucket with versioning enabled passes."""
|
|
548
|
+
mapper = S3ControlMapper()
|
|
549
|
+
bucket_data = {"Name": "test-bucket", "Versioning": {"Status": "Enabled"}}
|
|
550
|
+
|
|
551
|
+
result = mapper._assess_cp9(bucket_data)
|
|
552
|
+
assert result == "PASS"
|
|
553
|
+
|
|
554
|
+
def test_bucket_without_versioning_fails(self):
|
|
555
|
+
"""Test bucket without versioning fails."""
|
|
556
|
+
mapper = S3ControlMapper()
|
|
557
|
+
bucket_data = {"Name": "test-bucket", "Versioning": {"Status": "Disabled"}}
|
|
558
|
+
|
|
559
|
+
result = mapper._assess_cp9(bucket_data)
|
|
560
|
+
assert result == "FAIL"
|
|
561
|
+
|
|
562
|
+
def test_bucket_with_suspended_versioning_fails(self):
|
|
563
|
+
"""Test bucket with suspended versioning fails."""
|
|
564
|
+
mapper = S3ControlMapper()
|
|
565
|
+
bucket_data = {"Name": "test-bucket", "Versioning": {"Status": "Suspended"}}
|
|
566
|
+
|
|
567
|
+
result = mapper._assess_cp9(bucket_data)
|
|
568
|
+
assert result == "FAIL"
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
class TestGetControlDescription:
|
|
572
|
+
"""Test get_control_description method."""
|
|
573
|
+
|
|
574
|
+
def test_get_sc13_description(self):
|
|
575
|
+
"""Test getting SC-13 description."""
|
|
576
|
+
mapper = S3ControlMapper()
|
|
577
|
+
description = mapper.get_control_description("SC-13")
|
|
578
|
+
|
|
579
|
+
assert description is not None
|
|
580
|
+
assert "Cryptographic Protection" in description
|
|
581
|
+
|
|
582
|
+
def test_get_sc28_description(self):
|
|
583
|
+
"""Test getting SC-28 description."""
|
|
584
|
+
mapper = S3ControlMapper()
|
|
585
|
+
description = mapper.get_control_description("SC-28")
|
|
586
|
+
|
|
587
|
+
assert description is not None
|
|
588
|
+
assert "Protection of Information at Rest" in description
|
|
589
|
+
|
|
590
|
+
def test_get_ac3_description(self):
|
|
591
|
+
"""Test getting AC-3 description."""
|
|
592
|
+
mapper = S3ControlMapper()
|
|
593
|
+
description = mapper.get_control_description("AC-3")
|
|
594
|
+
|
|
595
|
+
assert description is not None
|
|
596
|
+
assert "Access Enforcement" in description
|
|
597
|
+
|
|
598
|
+
def test_get_unknown_control_description(self):
|
|
599
|
+
"""Test getting description for unknown control."""
|
|
600
|
+
mapper = S3ControlMapper()
|
|
601
|
+
description = mapper.get_control_description("UNKNOWN-1")
|
|
602
|
+
|
|
603
|
+
assert description is None
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
class TestGetMappedControls:
|
|
607
|
+
"""Test get_mapped_controls method."""
|
|
608
|
+
|
|
609
|
+
def test_get_mapped_controls(self):
|
|
610
|
+
"""Test getting all mapped controls."""
|
|
611
|
+
mapper = S3ControlMapper()
|
|
612
|
+
controls = mapper.get_mapped_controls()
|
|
613
|
+
|
|
614
|
+
assert len(controls) == 7
|
|
615
|
+
assert "SC-13" in controls
|
|
616
|
+
assert "SC-28" in controls
|
|
617
|
+
assert "AC-3" in controls
|
|
618
|
+
assert "AC-6" in controls
|
|
619
|
+
assert "AU-2" in controls
|
|
620
|
+
assert "AU-9" in controls
|
|
621
|
+
assert "CP-9" in controls
|
|
622
|
+
|
|
623
|
+
def test_controls_are_unique(self):
|
|
624
|
+
"""Test that returned controls are unique."""
|
|
625
|
+
mapper = S3ControlMapper()
|
|
626
|
+
controls = mapper.get_mapped_controls()
|
|
627
|
+
|
|
628
|
+
assert len(controls) == len(set(controls))
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
class TestGetCheckDetails:
|
|
632
|
+
"""Test get_check_details method."""
|
|
633
|
+
|
|
634
|
+
def test_get_sc13_check_details(self):
|
|
635
|
+
"""Test getting SC-13 check details."""
|
|
636
|
+
mapper = S3ControlMapper()
|
|
637
|
+
details = mapper.get_check_details("SC-13")
|
|
638
|
+
|
|
639
|
+
assert details is not None
|
|
640
|
+
assert "encryption_at_rest" in details
|
|
641
|
+
assert "encryption_algorithm" in details
|
|
642
|
+
assert details["encryption_at_rest"]["weight"] == 100
|
|
643
|
+
|
|
644
|
+
def test_get_ac3_check_details(self):
|
|
645
|
+
"""Test getting AC-3 check details."""
|
|
646
|
+
mapper = S3ControlMapper()
|
|
647
|
+
details = mapper.get_check_details("AC-3")
|
|
648
|
+
|
|
649
|
+
assert details is not None
|
|
650
|
+
assert "public_access_blocked" in details
|
|
651
|
+
assert "bucket_policy" in details
|
|
652
|
+
|
|
653
|
+
def test_get_unknown_control_check_details(self):
|
|
654
|
+
"""Test getting check details for unknown control."""
|
|
655
|
+
mapper = S3ControlMapper()
|
|
656
|
+
details = mapper.get_check_details("UNKNOWN-1")
|
|
657
|
+
|
|
658
|
+
assert details is None
|
|
659
|
+
|
|
660
|
+
def test_check_details_structure(self):
|
|
661
|
+
"""Test check details have required structure."""
|
|
662
|
+
mapper = S3ControlMapper()
|
|
663
|
+
details = mapper.get_check_details("SC-13")
|
|
664
|
+
|
|
665
|
+
for check_name, check_data in details.items():
|
|
666
|
+
assert "weight" in check_data
|
|
667
|
+
assert "pass_criteria" in check_data
|
|
668
|
+
assert "fail_criteria" in check_data
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
if __name__ == "__main__":
|
|
672
|
+
pytest.main([__file__, "-v"])
|