sraverify 0.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.
- sraverify/__init__.py +36 -0
- sraverify/checks/__init__.py +56 -0
- sraverify/checks/accessanalyzer/SRA_IAA_1.py +188 -0
- sraverify/checks/accessanalyzer/SRA_IAA_2.py +162 -0
- sraverify/checks/accessanalyzer/SRA_IAA_3.py +260 -0
- sraverify/checks/accessanalyzer/SRA_IAA_4.py +207 -0
- sraverify/checks/accessanalyzer/__init__.py +3 -0
- sraverify/checks/cloudtrail/SRA-CT-1.py +220 -0
- sraverify/checks/cloudtrail/SRA-CT-10.py +229 -0
- sraverify/checks/cloudtrail/SRA-CT-11.py +242 -0
- sraverify/checks/cloudtrail/SRA-CT-12.py +163 -0
- sraverify/checks/cloudtrail/SRA-CT-13.py +279 -0
- sraverify/checks/cloudtrail/SRA-CT-2.py +218 -0
- sraverify/checks/cloudtrail/SRA-CT-3.py +196 -0
- sraverify/checks/cloudtrail/SRA-CT-4.py +161 -0
- sraverify/checks/cloudtrail/SRA-CT-5.py +200 -0
- sraverify/checks/cloudtrail/SRA-CT-6.py +161 -0
- sraverify/checks/cloudtrail/SRA-CT-7.py +194 -0
- sraverify/checks/cloudtrail/SRA-CT-8.py +226 -0
- sraverify/checks/cloudtrail/SRA-CT-9.py +226 -0
- sraverify/checks/cloudtrail/__init__.py +3 -0
- sraverify/checks/config/SRA-CONFIG-1.py +197 -0
- sraverify/checks/config/__init__.py +3 -0
- sraverify/core/__init__.py +3 -0
- sraverify/core/check.py +227 -0
- sraverify/core/logging.py +37 -0
- sraverify/core/session.py +47 -0
- sraverify/lib/__init__.py +4 -0
- sraverify/lib/audit_info.py +37 -0
- sraverify/lib/banner.py +42 -0
- sraverify/lib/check_loader.py +80 -0
- sraverify/lib/org_mgmt_checker.py +86 -0
- sraverify/lib/outputs.py +46 -0
- sraverify/lib/progress.py +75 -0
- sraverify/lib/regions.py +27 -0
- sraverify/lib/session.py +23 -0
- sraverify/main.py +350 -0
- sraverify/services/__init__.py +3 -0
- sraverify/services/accessanalyzer/__init__.py +15 -0
- sraverify/services/accessanalyzer/base.py +123 -0
- sraverify/services/accessanalyzer/checks/__init__.py +3 -0
- sraverify/services/accessanalyzer/checks/sra_accessanalyzer_01.py +82 -0
- sraverify/services/accessanalyzer/checks/sra_accessanalyzer_02.py +82 -0
- sraverify/services/accessanalyzer/checks/sra_accessanalyzer_03.py +103 -0
- sraverify/services/accessanalyzer/checks/sra_accessanalyzer_04.py +139 -0
- sraverify/services/accessanalyzer/client.py +123 -0
- sraverify/services/account/__init__.py +9 -0
- sraverify/services/account/base.py +56 -0
- sraverify/services/account/checks/__init__.py +1 -0
- sraverify/services/account/checks/sra_account_01.py +65 -0
- sraverify/services/account/checks/sra_account_02.py +63 -0
- sraverify/services/account/checks/sra_account_03.py +63 -0
- sraverify/services/account/client.py +51 -0
- sraverify/services/auditmanager/__init__.py +10 -0
- sraverify/services/auditmanager/base.py +72 -0
- sraverify/services/auditmanager/checks/__init__.py +1 -0
- sraverify/services/auditmanager/checks/sra_auditmanager_01.py +58 -0
- sraverify/services/auditmanager/checks/sra_auditmanager_02.py +80 -0
- sraverify/services/auditmanager/client.py +58 -0
- sraverify/services/cloudtrail/__init__.py +33 -0
- sraverify/services/cloudtrail/base.py +167 -0
- sraverify/services/cloudtrail/checks/__init__.py +1 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_01.py +83 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_02.py +99 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_03.py +94 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_04.py +92 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_05.py +106 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_06.py +93 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_07.py +96 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_08.py +145 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_09.py +167 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_10.py +162 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_11.py +178 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_12.py +77 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_13.py +120 -0
- sraverify/services/cloudtrail/client.py +118 -0
- sraverify/services/config/__init__.py +25 -0
- sraverify/services/config/base.py +249 -0
- sraverify/services/config/checks/__init__.py +1 -0
- sraverify/services/config/checks/sra_config_01.py +123 -0
- sraverify/services/config/checks/sra_config_02.py +156 -0
- sraverify/services/config/checks/sra_config_03.py +149 -0
- sraverify/services/config/checks/sra_config_04.py +104 -0
- sraverify/services/config/checks/sra_config_05.py +104 -0
- sraverify/services/config/checks/sra_config_06.py +194 -0
- sraverify/services/config/checks/sra_config_07.py +162 -0
- sraverify/services/config/checks/sra_config_08.py +185 -0
- sraverify/services/config/checks/sra_config_09.py +177 -0
- sraverify/services/config/client.py +264 -0
- sraverify/services/ec2/__init__.py +8 -0
- sraverify/services/ec2/base.py +75 -0
- sraverify/services/ec2/checks/__init__.py +1 -0
- sraverify/services/ec2/checks/sra_ec2_01.py +83 -0
- sraverify/services/ec2/client.py +63 -0
- sraverify/services/firewallmanager/__init__.py +23 -0
- sraverify/services/firewallmanager/base.py +48 -0
- sraverify/services/firewallmanager/checks/__init__.py +1 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_01.py +75 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_02.py +57 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_03.py +51 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_04.py +51 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_05.py +51 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_06.py +51 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_07.py +51 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_08.py +61 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_09.py +61 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_10.py +71 -0
- sraverify/services/firewallmanager/client.py +40 -0
- sraverify/services/guardduty/__init__.py +58 -0
- sraverify/services/guardduty/base.py +207 -0
- sraverify/services/guardduty/checks/__init__.py +3 -0
- sraverify/services/guardduty/checks/sra_guardduty_01.py +51 -0
- sraverify/services/guardduty/checks/sra_guardduty_02.py +80 -0
- sraverify/services/guardduty/checks/sra_guardduty_03.py +77 -0
- sraverify/services/guardduty/checks/sra_guardduty_04.py +84 -0
- sraverify/services/guardduty/checks/sra_guardduty_05.py +84 -0
- sraverify/services/guardduty/checks/sra_guardduty_06.py +84 -0
- sraverify/services/guardduty/checks/sra_guardduty_07.py +85 -0
- sraverify/services/guardduty/checks/sra_guardduty_08.py +83 -0
- sraverify/services/guardduty/checks/sra_guardduty_09.py +84 -0
- sraverify/services/guardduty/checks/sra_guardduty_10.py +83 -0
- sraverify/services/guardduty/checks/sra_guardduty_11.py +93 -0
- sraverify/services/guardduty/checks/sra_guardduty_12.py +83 -0
- sraverify/services/guardduty/checks/sra_guardduty_13.py +90 -0
- sraverify/services/guardduty/checks/sra_guardduty_14.py +136 -0
- sraverify/services/guardduty/checks/sra_guardduty_15.py +94 -0
- sraverify/services/guardduty/checks/sra_guardduty_16.py +94 -0
- sraverify/services/guardduty/checks/sra_guardduty_17.py +91 -0
- sraverify/services/guardduty/checks/sra_guardduty_18.py +91 -0
- sraverify/services/guardduty/checks/sra_guardduty_19.py +91 -0
- sraverify/services/guardduty/checks/sra_guardduty_20.py +111 -0
- sraverify/services/guardduty/checks/sra_guardduty_21.py +112 -0
- sraverify/services/guardduty/checks/sra_guardduty_22.py +111 -0
- sraverify/services/guardduty/checks/sra_guardduty_23.py +154 -0
- sraverify/services/guardduty/checks/sra_guardduty_24.py +111 -0
- sraverify/services/guardduty/checks/sra_guardduty_25.py +111 -0
- sraverify/services/guardduty/client.py +107 -0
- sraverify/services/inspector/__init__.py +29 -0
- sraverify/services/inspector/base.py +233 -0
- sraverify/services/inspector/checks/__init__.py +3 -0
- sraverify/services/inspector/checks/sra_inspector_01.py +69 -0
- sraverify/services/inspector/checks/sra_inspector_02.py +68 -0
- sraverify/services/inspector/checks/sra_inspector_03.py +68 -0
- sraverify/services/inspector/checks/sra_inspector_04.py +70 -0
- sraverify/services/inspector/checks/sra_inspector_05.py +69 -0
- sraverify/services/inspector/checks/sra_inspector_06.py +115 -0
- sraverify/services/inspector/checks/sra_inspector_07.py +109 -0
- sraverify/services/inspector/checks/sra_inspector_08.py +69 -0
- sraverify/services/inspector/checks/sra_inspector_09.py +69 -0
- sraverify/services/inspector/checks/sra_inspector_10.py +69 -0
- sraverify/services/inspector/checks/sra_inspector_11.py +69 -0
- sraverify/services/inspector/client.py +99 -0
- sraverify/services/macie/__init__.py +27 -0
- sraverify/services/macie/base.py +271 -0
- sraverify/services/macie/checks/__init__.py +1 -0
- sraverify/services/macie/checks/sra_macie_01.py +100 -0
- sraverify/services/macie/checks/sra_macie_02.py +102 -0
- sraverify/services/macie/checks/sra_macie_03.py +152 -0
- sraverify/services/macie/checks/sra_macie_04.py +120 -0
- sraverify/services/macie/checks/sra_macie_05.py +85 -0
- sraverify/services/macie/checks/sra_macie_06.py +124 -0
- sraverify/services/macie/checks/sra_macie_07.py +138 -0
- sraverify/services/macie/checks/sra_macie_08.py +82 -0
- sraverify/services/macie/checks/sra_macie_09.py +103 -0
- sraverify/services/macie/checks/sra_macie_10.py +81 -0
- sraverify/services/macie/client.py +220 -0
- sraverify/services/s3/__init__.py +16 -0
- sraverify/services/s3/base.py +69 -0
- sraverify/services/s3/checks/__init__.py +1 -0
- sraverify/services/s3/checks/sra_s3_01.py +89 -0
- sraverify/services/s3/checks/sra_s3_02.py +89 -0
- sraverify/services/s3/checks/sra_s3_03.py +88 -0
- sraverify/services/s3/checks/sra_s3_04.py +88 -0
- sraverify/services/s3/client.py +52 -0
- sraverify/services/securityhub/__init__.py +27 -0
- sraverify/services/securityhub/base.py +349 -0
- sraverify/services/securityhub/checks/__init__.py +1 -0
- sraverify/services/securityhub/checks/sra_securityhub_01.py +115 -0
- sraverify/services/securityhub/checks/sra_securityhub_02.py +114 -0
- sraverify/services/securityhub/checks/sra_securityhub_03.py +136 -0
- sraverify/services/securityhub/checks/sra_securityhub_04.py +75 -0
- sraverify/services/securityhub/checks/sra_securityhub_05.py +102 -0
- sraverify/services/securityhub/checks/sra_securityhub_06.py +113 -0
- sraverify/services/securityhub/checks/sra_securityhub_07.py +121 -0
- sraverify/services/securityhub/checks/sra_securityhub_08.py +113 -0
- sraverify/services/securityhub/checks/sra_securityhub_09.py +100 -0
- sraverify/services/securityhub/checks/sra_securityhub_10.py +94 -0
- sraverify/services/securityhub/checks/sra_securityhub_11.py +73 -0
- sraverify/services/securityhub/client.py +249 -0
- sraverify/services/securityincidentresponse/__init__.py +13 -0
- sraverify/services/securityincidentresponse/base.py +95 -0
- sraverify/services/securityincidentresponse/checks/__init__.py +1 -0
- sraverify/services/securityincidentresponse/checks/sra_securityincidentresponse_01.py +77 -0
- sraverify/services/securityincidentresponse/checks/sra_securityincidentresponse_02.py +72 -0
- sraverify/services/securityincidentresponse/checks/sra_securityincidentresponse_03.py +86 -0
- sraverify/services/securityincidentresponse/checks/sra_securityincidentresponse_04.py +117 -0
- sraverify/services/securityincidentresponse/checks/sra_securityincidentresponse_05.py +55 -0
- sraverify/services/securityincidentresponse/client.py +71 -0
- sraverify/services/securitylake/__init__.py +39 -0
- sraverify/services/securitylake/base.py +461 -0
- sraverify/services/securitylake/checks/__init__.py +1 -0
- sraverify/services/securitylake/checks/sra_securitylake_01.py +98 -0
- sraverify/services/securitylake/checks/sra_securitylake_02.py +133 -0
- sraverify/services/securitylake/checks/sra_securitylake_03.py +116 -0
- sraverify/services/securitylake/checks/sra_securitylake_04.py +72 -0
- sraverify/services/securitylake/checks/sra_securitylake_05.py +116 -0
- sraverify/services/securitylake/checks/sra_securitylake_06.py +104 -0
- sraverify/services/securitylake/checks/sra_securitylake_07.py +108 -0
- sraverify/services/securitylake/checks/sra_securitylake_08.py +107 -0
- sraverify/services/securitylake/checks/sra_securitylake_09.py +107 -0
- sraverify/services/securitylake/checks/sra_securitylake_10.py +106 -0
- sraverify/services/securitylake/checks/sra_securitylake_11.py +109 -0
- sraverify/services/securitylake/checks/sra_securitylake_12.py +108 -0
- sraverify/services/securitylake/checks/sra_securitylake_13.py +108 -0
- sraverify/services/securitylake/checks/sra_securitylake_14.py +72 -0
- sraverify/services/securitylake/checks/sra_securitylake_15.py +120 -0
- sraverify/services/securitylake/checks/sra_securitylake_16.py +104 -0
- sraverify/services/securitylake/checks/sra_securitylake_17.py +103 -0
- sraverify/services/securitylake/client.py +247 -0
- sraverify/services/shield/__init__.py +33 -0
- sraverify/services/shield/base.py +199 -0
- sraverify/services/shield/checks/__init__.py +1 -0
- sraverify/services/shield/checks/sra_shield_01.py +68 -0
- sraverify/services/shield/checks/sra_shield_02.py +77 -0
- sraverify/services/shield/checks/sra_shield_03.py +84 -0
- sraverify/services/shield/checks/sra_shield_04.py +84 -0
- sraverify/services/shield/checks/sra_shield_05.py +84 -0
- sraverify/services/shield/checks/sra_shield_06.py +84 -0
- sraverify/services/shield/checks/sra_shield_07.py +84 -0
- sraverify/services/shield/checks/sra_shield_08.py +69 -0
- sraverify/services/shield/checks/sra_shield_09.py +86 -0
- sraverify/services/shield/checks/sra_shield_10.py +100 -0
- sraverify/services/shield/checks/sra_shield_11.py +71 -0
- sraverify/services/shield/checks/sra_shield_12.py +130 -0
- sraverify/services/shield/checks/sra_shield_13.py +112 -0
- sraverify/services/shield/checks/sra_shield_14.py +111 -0
- sraverify/services/shield/client.py +214 -0
- sraverify/services/waf/__init__.py +21 -0
- sraverify/services/waf/base.py +100 -0
- sraverify/services/waf/checks/__init__.py +1 -0
- sraverify/services/waf/checks/sra_waf_01.py +63 -0
- sraverify/services/waf/checks/sra_waf_02.py +82 -0
- sraverify/services/waf/checks/sra_waf_03.py +123 -0
- sraverify/services/waf/checks/sra_waf_04.py +94 -0
- sraverify/services/waf/checks/sra_waf_05.py +94 -0
- sraverify/services/waf/checks/sra_waf_06.py +91 -0
- sraverify/services/waf/checks/sra_waf_07.py +94 -0
- sraverify/services/waf/checks/sra_waf_08.py +66 -0
- sraverify/services/waf/checks/sra_waf_09.py +95 -0
- sraverify/services/waf/client.py +109 -0
- sraverify/utils/__init__.py +3 -0
- sraverify/utils/banner.py +65 -0
- sraverify/utils/outputs.py +57 -0
- sraverify/utils/progress.py +97 -0
- sraverify-0.1.0.dist-info/LICENSE +175 -0
- sraverify-0.1.0.dist-info/METADATA +516 -0
- sraverify-0.1.0.dist-info/NOTICE +1 -0
- sraverify-0.1.0.dist-info/RECORD +261 -0
- sraverify-0.1.0.dist-info/WHEEL +5 -0
- sraverify-0.1.0.dist-info/entry_points.txt +2 -0
- sraverify-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SRA-MACIE-10: Macie member account limit not reached.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from sraverify.services.macie.base import MacieCheck
|
|
6
|
+
from sraverify.core.logging import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SRA_MACIE_10(MacieCheck):
|
|
10
|
+
"""Check if Macie member account limit not reached."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
"""Initialize the check."""
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.check_id = "SRA-MACIE-10"
|
|
16
|
+
self.check_name = "Macie member account limit not reached"
|
|
17
|
+
self.description = (
|
|
18
|
+
"This check verifies whether the maximum number of allowed member accounts are already associated with the "
|
|
19
|
+
"delegated administrator account for the AWS Organization."
|
|
20
|
+
)
|
|
21
|
+
self.severity = "MEDIUM"
|
|
22
|
+
self.account_type = "audit"
|
|
23
|
+
self.check_logic = "Check runs macie2 describe-organization-configuration. PASS if maxaccountlimitreached = False"
|
|
24
|
+
self.resource_type = "AWS::Macie::Session"
|
|
25
|
+
|
|
26
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
27
|
+
"""
|
|
28
|
+
Execute the check.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
List of findings
|
|
32
|
+
"""
|
|
33
|
+
findings = []
|
|
34
|
+
|
|
35
|
+
for region in self.regions:
|
|
36
|
+
# Get organization configuration using the base class method with caching
|
|
37
|
+
org_config = self.get_organization_configuration(region)
|
|
38
|
+
|
|
39
|
+
# Check if the API call was successful
|
|
40
|
+
if not org_config:
|
|
41
|
+
findings.append(
|
|
42
|
+
self.create_finding(
|
|
43
|
+
status="FAIL",
|
|
44
|
+
region=region,
|
|
45
|
+
resource_id=f"macie2/{self.account_id}/{region}",
|
|
46
|
+
checked_value="maxAccountLimitReached: false",
|
|
47
|
+
actual_value="Failed to retrieve Macie organization configuration",
|
|
48
|
+
remediation="Ensure Macie is enabled and you have the necessary permissions to call the Macie DescribeOrganizationConfiguration API"
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
continue
|
|
52
|
+
|
|
53
|
+
# Check if max account limit is reached
|
|
54
|
+
max_account_limit_reached = org_config.get('maxAccountLimitReached', False)
|
|
55
|
+
|
|
56
|
+
if not max_account_limit_reached:
|
|
57
|
+
findings.append(
|
|
58
|
+
self.create_finding(
|
|
59
|
+
status="PASS",
|
|
60
|
+
region=region,
|
|
61
|
+
resource_id=f"macie2/{self.account_id}/{region}",
|
|
62
|
+
checked_value="maxAccountLimitReached: false",
|
|
63
|
+
actual_value=f"Macie member account limit not reached in region {region}",
|
|
64
|
+
remediation="No remediation needed"
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
else:
|
|
68
|
+
findings.append(
|
|
69
|
+
self.create_finding(
|
|
70
|
+
status="FAIL",
|
|
71
|
+
region=region,
|
|
72
|
+
resource_id=f"macie2/{self.account_id}/{region}",
|
|
73
|
+
checked_value="maxAccountLimitReached: false",
|
|
74
|
+
actual_value=f"Macie member account limit reached in region {region}",
|
|
75
|
+
remediation=(
|
|
76
|
+
"Contact AWS Support to request an increase in the Macie member account limit"
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return findings
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Macie client for interacting with AWS Macie service.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Dict, List, Optional, Any
|
|
5
|
+
import boto3
|
|
6
|
+
from botocore.exceptions import ClientError
|
|
7
|
+
from sraverify.core.logging import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MacieClient:
|
|
11
|
+
"""Client for interacting with AWS Macie service."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, region: str, session: Optional[boto3.Session] = None):
|
|
14
|
+
"""
|
|
15
|
+
Initialize Macie client for a specific region.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
region: AWS region name
|
|
19
|
+
session: AWS session to use (if None, a new session will be created)
|
|
20
|
+
"""
|
|
21
|
+
self.region = region
|
|
22
|
+
self.session = session or boto3.Session()
|
|
23
|
+
self.client = self.session.client('macie2', region_name=region)
|
|
24
|
+
self.org_client = self.session.client('organizations', region_name=region)
|
|
25
|
+
|
|
26
|
+
def get_findings_publication_configuration(self) -> Dict[str, Any]:
|
|
27
|
+
"""
|
|
28
|
+
Get the findings publication configuration for Macie.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Dictionary containing findings publication configuration
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
logger.debug(f"Getting Macie findings publication configuration in {self.region}")
|
|
35
|
+
response = self.client.get_findings_publication_configuration()
|
|
36
|
+
logger.debug(f"Macie findings publication configuration in {self.region}: {response}")
|
|
37
|
+
return response
|
|
38
|
+
except ClientError as e:
|
|
39
|
+
error_code = getattr(e, 'response', {}).get('Error', {}).get('Code', '')
|
|
40
|
+
error_message = str(e)
|
|
41
|
+
if error_code == 'AccessDeniedException' or 'Macie is not enabled' in error_message:
|
|
42
|
+
# If we get an access denied exception, it might be because Macie is not enabled
|
|
43
|
+
# in this region for this account
|
|
44
|
+
logger.debug(f"Access denied when getting Macie findings publication configuration in {self.region}. Macie might not be enabled in this region.")
|
|
45
|
+
else:
|
|
46
|
+
logger.debug(f"Error getting Macie findings publication configuration in {self.region}: {e}")
|
|
47
|
+
return {}
|
|
48
|
+
except Exception as e:
|
|
49
|
+
logger.debug(f"Unexpected error getting Macie findings publication configuration in {self.region}: {e}")
|
|
50
|
+
return {}
|
|
51
|
+
|
|
52
|
+
def get_classification_export_configuration(self) -> Dict[str, Any]:
|
|
53
|
+
"""
|
|
54
|
+
Get the classification export configuration for Macie.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Dictionary containing classification export configuration
|
|
58
|
+
"""
|
|
59
|
+
try:
|
|
60
|
+
logger.debug(f"Getting Macie classification export configuration in {self.region}")
|
|
61
|
+
response = self.client.get_classification_export_configuration()
|
|
62
|
+
logger.debug(f"Macie classification export configuration in {self.region}: {response}")
|
|
63
|
+
return response
|
|
64
|
+
except ClientError as e:
|
|
65
|
+
error_code = getattr(e, 'response', {}).get('Error', {}).get('Code', '')
|
|
66
|
+
error_message = str(e)
|
|
67
|
+
if error_code == 'AccessDeniedException' or 'Macie is not enabled' in error_message:
|
|
68
|
+
logger.debug(f"Access denied when getting Macie classification export configuration in {self.region}. Macie might not be enabled in this region.")
|
|
69
|
+
else:
|
|
70
|
+
logger.debug(f"Error getting Macie classification export configuration in {self.region}: {e}")
|
|
71
|
+
return {}
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.debug(f"Unexpected error getting Macie classification export configuration in {self.region}: {e}")
|
|
74
|
+
return {}
|
|
75
|
+
|
|
76
|
+
def list_delegated_administrators(self, service_principal: str = "macie.amazonaws.com") -> List[Dict[str, Any]]:
|
|
77
|
+
"""
|
|
78
|
+
List delegated administrators for Macie.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
service_principal: Service principal to check for delegated administrators
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
List of delegated administrators
|
|
85
|
+
"""
|
|
86
|
+
try:
|
|
87
|
+
logger.debug(f"Listing delegated administrators for {service_principal} in {self.region}")
|
|
88
|
+
response = self.org_client.list_delegated_administrators(ServicePrincipal=service_principal)
|
|
89
|
+
delegated_admins = response.get('DelegatedAdministrators', [])
|
|
90
|
+
logger.debug(f"Found {len(delegated_admins)} delegated administrators for {service_principal}")
|
|
91
|
+
for admin in delegated_admins:
|
|
92
|
+
logger.debug(f"Delegated admin: {admin.get('Id')} - {admin.get('Name')}")
|
|
93
|
+
return delegated_admins
|
|
94
|
+
except ClientError as e:
|
|
95
|
+
logger.error(f"Error listing delegated administrators for {service_principal}: {e}")
|
|
96
|
+
return []
|
|
97
|
+
except Exception as e:
|
|
98
|
+
logger.error(f"Unexpected error listing delegated administrators: {e}")
|
|
99
|
+
return []
|
|
100
|
+
|
|
101
|
+
def list_members(self) -> List[Dict[str, Any]]:
|
|
102
|
+
"""
|
|
103
|
+
List Macie members.
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
List of Macie members
|
|
107
|
+
"""
|
|
108
|
+
try:
|
|
109
|
+
logger.debug(f"Listing Macie members in {self.region}")
|
|
110
|
+
members = []
|
|
111
|
+
paginator = self.client.get_paginator('list_members')
|
|
112
|
+
|
|
113
|
+
for page in paginator.paginate():
|
|
114
|
+
members.extend(page.get('members', []))
|
|
115
|
+
|
|
116
|
+
logger.debug(f"Found {len(members)} Macie members in {self.region}")
|
|
117
|
+
return members
|
|
118
|
+
except ClientError as e:
|
|
119
|
+
error_code = getattr(e, 'response', {}).get('Error', {}).get('Code', '')
|
|
120
|
+
error_message = str(e)
|
|
121
|
+
if error_code == 'AccessDeniedException' or 'Macie is not enabled' in error_message:
|
|
122
|
+
logger.debug(f"Access denied when listing Macie members in {self.region}. This is expected if the account is not a Macie admin account or Macie is not enabled.")
|
|
123
|
+
else:
|
|
124
|
+
logger.debug(f"Error listing Macie members in {self.region}: {e}")
|
|
125
|
+
return []
|
|
126
|
+
except Exception as e:
|
|
127
|
+
logger.debug(f"Unexpected error listing Macie members in {self.region}: {e}")
|
|
128
|
+
return []
|
|
129
|
+
|
|
130
|
+
def list_organization_accounts(self) -> List[Dict[str, Any]]:
|
|
131
|
+
"""
|
|
132
|
+
List all accounts in the AWS Organization.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
List of accounts in the AWS Organization
|
|
136
|
+
"""
|
|
137
|
+
try:
|
|
138
|
+
logger.debug(f"Listing AWS Organization accounts in {self.region}")
|
|
139
|
+
accounts = []
|
|
140
|
+
paginator = self.org_client.get_paginator('list_accounts')
|
|
141
|
+
|
|
142
|
+
for page in paginator.paginate():
|
|
143
|
+
accounts.extend(page.get('Accounts', []))
|
|
144
|
+
|
|
145
|
+
logger.debug(f"Found {len(accounts)} AWS Organization accounts")
|
|
146
|
+
return accounts
|
|
147
|
+
except ClientError as e:
|
|
148
|
+
logger.error(f"Error listing AWS Organization accounts: {e}")
|
|
149
|
+
return []
|
|
150
|
+
except Exception as e:
|
|
151
|
+
logger.error(f"Unexpected error listing AWS Organization accounts: {e}")
|
|
152
|
+
return []
|
|
153
|
+
|
|
154
|
+
def describe_organization_configuration(self) -> Dict[str, Any]:
|
|
155
|
+
"""
|
|
156
|
+
Describe the Macie organization configuration.
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Dictionary containing Macie organization configuration
|
|
160
|
+
"""
|
|
161
|
+
try:
|
|
162
|
+
logger.debug(f"Describing Macie organization configuration in {self.region}")
|
|
163
|
+
response = self.client.describe_organization_configuration()
|
|
164
|
+
logger.debug(f"Macie organization configuration in {self.region}: {response}")
|
|
165
|
+
return response
|
|
166
|
+
except ClientError as e:
|
|
167
|
+
error_code = getattr(e, 'response', {}).get('Error', {}).get('Code', '')
|
|
168
|
+
error_message = str(e)
|
|
169
|
+
if error_code == 'AccessDeniedException' or 'Macie is not enabled' in error_message or 'must be the Macie administrator' in error_message:
|
|
170
|
+
logger.debug(f"Access denied when describing Macie organization configuration in {self.region}. This is expected if the account is not a Macie admin account or Macie is not enabled.")
|
|
171
|
+
else:
|
|
172
|
+
logger.debug(f"Error describing Macie organization configuration in {self.region}: {e}")
|
|
173
|
+
return {}
|
|
174
|
+
except Exception as e:
|
|
175
|
+
logger.debug(f"Unexpected error describing Macie organization configuration in {self.region}: {e}")
|
|
176
|
+
return {}
|
|
177
|
+
|
|
178
|
+
def get_account_id(self) -> Optional[str]:
|
|
179
|
+
"""
|
|
180
|
+
Get the current account ID.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
Current account ID or None if not available
|
|
184
|
+
"""
|
|
185
|
+
try:
|
|
186
|
+
logger.debug(f"Getting current account ID in {self.region}")
|
|
187
|
+
sts_client = self.session.client("sts")
|
|
188
|
+
response = sts_client.get_caller_identity()
|
|
189
|
+
account_id = response["Account"]
|
|
190
|
+
logger.debug(f"Current account ID: {account_id}")
|
|
191
|
+
return account_id
|
|
192
|
+
except ClientError as e:
|
|
193
|
+
logger.error(f"Error getting current account ID: {e}")
|
|
194
|
+
return None
|
|
195
|
+
except Exception as e:
|
|
196
|
+
logger.error(f"Unexpected error getting current account ID: {e}")
|
|
197
|
+
return None
|
|
198
|
+
def get_administrator_account(self) -> Dict[str, Any]:
|
|
199
|
+
"""
|
|
200
|
+
Get the Macie administrator account.
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Dictionary containing Macie administrator account information
|
|
204
|
+
"""
|
|
205
|
+
try:
|
|
206
|
+
logger.debug(f"Getting Macie administrator account in {self.region}")
|
|
207
|
+
response = self.client.get_administrator_account()
|
|
208
|
+
logger.debug(f"Macie administrator account in {self.region}: {response}")
|
|
209
|
+
return response
|
|
210
|
+
except ClientError as e:
|
|
211
|
+
error_code = getattr(e, 'response', {}).get('Error', {}).get('Code', '')
|
|
212
|
+
error_message = str(e)
|
|
213
|
+
if error_code == 'AccessDeniedException' or 'Macie is not enabled' in error_message:
|
|
214
|
+
logger.debug(f"Access denied when getting Macie administrator account in {self.region}. Macie might not be enabled in this region.")
|
|
215
|
+
else:
|
|
216
|
+
logger.debug(f"Error getting Macie administrator account in {self.region}: {e}")
|
|
217
|
+
return {}
|
|
218
|
+
except Exception as e:
|
|
219
|
+
logger.debug(f"Unexpected error getting Macie administrator account in {self.region}: {e}")
|
|
220
|
+
return {}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""
|
|
2
|
+
S3 security checks.
|
|
3
|
+
"""
|
|
4
|
+
from sraverify.services.s3.checks.sra_s3_01 import SRA_S3_01
|
|
5
|
+
from sraverify.services.s3.checks.sra_s3_02 import SRA_S3_02
|
|
6
|
+
from sraverify.services.s3.checks.sra_s3_03 import SRA_S3_03
|
|
7
|
+
from sraverify.services.s3.checks.sra_s3_04 import SRA_S3_04
|
|
8
|
+
|
|
9
|
+
# Register checks
|
|
10
|
+
CHECKS = {
|
|
11
|
+
"SRA-S3-01": SRA_S3_01,
|
|
12
|
+
"SRA-S3-02": SRA_S3_02,
|
|
13
|
+
"SRA-S3-03": SRA_S3_03,
|
|
14
|
+
"SRA-S3-04": SRA_S3_04,
|
|
15
|
+
|
|
16
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base class for S3 security checks.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Optional, Dict, Any
|
|
5
|
+
from sraverify.core.check import SecurityCheck
|
|
6
|
+
from sraverify.services.s3.client import S3Client
|
|
7
|
+
from sraverify.core.logging import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class S3Check(SecurityCheck):
|
|
11
|
+
"""Base class for all S3 security checks."""
|
|
12
|
+
|
|
13
|
+
# Class-level cache shared across all instances
|
|
14
|
+
_public_access_cache = {}
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
"""Initialize S3 base check."""
|
|
18
|
+
super().__init__(
|
|
19
|
+
account_type="application",
|
|
20
|
+
service="S3",
|
|
21
|
+
resource_type="AWS::S3::AccountPublicAccessBlock"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def _setup_clients(self):
|
|
25
|
+
"""Set up S3 clients for each region."""
|
|
26
|
+
# Clear existing clients
|
|
27
|
+
self._clients.clear()
|
|
28
|
+
# Set up new clients only if regions are initialized
|
|
29
|
+
if hasattr(self, 'regions') and self.regions:
|
|
30
|
+
for region in self.regions:
|
|
31
|
+
self._clients[region] = S3Client(region, session=self.session)
|
|
32
|
+
|
|
33
|
+
def get_public_access(self) -> Dict[str, Any]:
|
|
34
|
+
"""
|
|
35
|
+
Get the public access block configuration for the account with caching.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Public access block configuration
|
|
39
|
+
"""
|
|
40
|
+
if not self.regions:
|
|
41
|
+
logger.warning("No regions specified")
|
|
42
|
+
return {}
|
|
43
|
+
|
|
44
|
+
account_id = self.account_id
|
|
45
|
+
if not account_id:
|
|
46
|
+
logger.warning("Could not determine account ID")
|
|
47
|
+
return {}
|
|
48
|
+
|
|
49
|
+
# Use session region name as part of cache key
|
|
50
|
+
cache_key = f"public_access:{account_id}:{self.session.region_name}"
|
|
51
|
+
if cache_key in self.__class__._public_access_cache:
|
|
52
|
+
logger.debug(f"Using cached public access block configuration for {cache_key}")
|
|
53
|
+
return self.__class__._public_access_cache[cache_key]
|
|
54
|
+
|
|
55
|
+
# Use any region to get public access block configuration
|
|
56
|
+
# S3 is a global service, but we need to use a regional endpoint
|
|
57
|
+
client = self._clients.get(self.regions[0])
|
|
58
|
+
if not client:
|
|
59
|
+
logger.warning("No S3 client available")
|
|
60
|
+
return {}
|
|
61
|
+
|
|
62
|
+
# Get public access block configuration from client
|
|
63
|
+
public_access_config = client.get_public_access_block(account_id)
|
|
64
|
+
|
|
65
|
+
# Cache the results
|
|
66
|
+
self.__class__._public_access_cache[cache_key] = public_access_config
|
|
67
|
+
logger.debug(f"Cached public access block configuration for {cache_key}")
|
|
68
|
+
|
|
69
|
+
return public_access_config
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""S3 service checks."""
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SRA-S3-01: S3 restrict public bucket is enabled.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from sraverify.services.s3.base import S3Check
|
|
6
|
+
from sraverify.core.logging import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SRA_S3_01(S3Check):
|
|
10
|
+
"""Check if S3 restrict public bucket is enabled for the account."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
"""Initialize the check."""
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.check_id = "SRA-S3-01"
|
|
16
|
+
self.check_name = "S3 restrict public bucket is enabled"
|
|
17
|
+
self.account_type = "application"
|
|
18
|
+
self.severity = "HIGH"
|
|
19
|
+
self.description = (
|
|
20
|
+
"This check verifies whether S3 should restrict public policies for S3 buckets. "
|
|
21
|
+
"Setting this restricts access to this bucket to only AWS service principals and authorized users "
|
|
22
|
+
"within this account if the bucket has a public policy."
|
|
23
|
+
)
|
|
24
|
+
self.check_logic = (
|
|
25
|
+
"Check if RestrictPublicBuckets is set to true in the account's public access block configuration."
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
29
|
+
"""
|
|
30
|
+
Execute the check.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
List of findings
|
|
34
|
+
"""
|
|
35
|
+
findings = []
|
|
36
|
+
|
|
37
|
+
# Get public access block configuration using the base class method
|
|
38
|
+
# This will use the cache if available or make API calls if needed
|
|
39
|
+
public_access_config = self.get_public_access()
|
|
40
|
+
|
|
41
|
+
# Check if the configuration exists and RestrictPublicBuckets is enabled
|
|
42
|
+
if not public_access_config:
|
|
43
|
+
findings.append(
|
|
44
|
+
self.create_finding(
|
|
45
|
+
status="FAIL",
|
|
46
|
+
region="global", # S3 public access block is a global setting
|
|
47
|
+
resource_id=self.account_id,
|
|
48
|
+
checked_value="RestrictPublicBuckets: true",
|
|
49
|
+
actual_value="No public access block configuration found",
|
|
50
|
+
remediation=(
|
|
51
|
+
"Enable S3 Block Public Access at the account level using the AWS CLI command: "
|
|
52
|
+
f"aws s3control put-public-access-block --account-id {self.account_id} "
|
|
53
|
+
"--public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,"
|
|
54
|
+
"BlockPublicPolicy=true,RestrictPublicBuckets=true"
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
return findings
|
|
59
|
+
|
|
60
|
+
restrict_public_buckets = public_access_config.get('RestrictPublicBuckets', False)
|
|
61
|
+
|
|
62
|
+
if restrict_public_buckets:
|
|
63
|
+
findings.append(
|
|
64
|
+
self.create_finding(
|
|
65
|
+
status="PASS",
|
|
66
|
+
region="global", # S3 public access block is a global setting
|
|
67
|
+
resource_id=self.account_id,
|
|
68
|
+
checked_value="RestrictPublicBuckets: true",
|
|
69
|
+
actual_value="RestrictPublicBuckets setting is true",
|
|
70
|
+
remediation="No remediation needed"
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
findings.append(
|
|
75
|
+
self.create_finding(
|
|
76
|
+
status="FAIL",
|
|
77
|
+
region="global", # S3 public access block is a global setting
|
|
78
|
+
resource_id=self.account_id,
|
|
79
|
+
checked_value="RestrictPublicBuckets: true",
|
|
80
|
+
actual_value="RestrictPublicBuckets setting is false",
|
|
81
|
+
remediation=(
|
|
82
|
+
"Enable S3 Restrict Public Buckets at the account level using the AWS CLI command: "
|
|
83
|
+
f"aws s3control put-public-access-block --account-id {self.account_id} "
|
|
84
|
+
"--public-access-block-configuration RestrictPublicBuckets=true"
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return findings
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SRA-S3-02: S3 block public ACLs is set.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from sraverify.services.s3.base import S3Check
|
|
6
|
+
from sraverify.core.logging import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SRA_S3_02(S3Check):
|
|
10
|
+
"""Check if S3 block public ACLs is enabled for the account."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
"""Initialize the check."""
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.check_id = "SRA-S3-02"
|
|
16
|
+
self.check_name = "S3 block public ACLs is set"
|
|
17
|
+
self.account_type = "application"
|
|
18
|
+
self.severity = "HIGH"
|
|
19
|
+
self.description = (
|
|
20
|
+
"This check verifies whether S3 public block access control lists (ACLs) for buckets and object is enabled. "
|
|
21
|
+
"Setting this fails prevents from setting a public ACL on S3 buckets and Objects. It also prevent creating "
|
|
22
|
+
"a bucket with public ACL and uploading a object with public ACL."
|
|
23
|
+
)
|
|
24
|
+
self.check_logic = (
|
|
25
|
+
"Check if BlockPublicAcls is set to true in the account's public access block configuration."
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
29
|
+
"""
|
|
30
|
+
Execute the check.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
List of findings
|
|
34
|
+
"""
|
|
35
|
+
findings = []
|
|
36
|
+
|
|
37
|
+
# Get public access block configuration using the base class method
|
|
38
|
+
# This will use the cache if available or make API calls if needed
|
|
39
|
+
public_access_config = self.get_public_access()
|
|
40
|
+
|
|
41
|
+
# Check if the configuration exists and BlockPublicAcls is enabled
|
|
42
|
+
if not public_access_config:
|
|
43
|
+
findings.append(
|
|
44
|
+
self.create_finding(
|
|
45
|
+
status="FAIL",
|
|
46
|
+
region="global", # S3 public access block is a global setting
|
|
47
|
+
resource_id=self.account_id,
|
|
48
|
+
checked_value="BlockPublicAcls: true",
|
|
49
|
+
actual_value="No public access block configuration found",
|
|
50
|
+
remediation=(
|
|
51
|
+
"Enable S3 Block Public Access at the account level using the AWS CLI command: "
|
|
52
|
+
f"aws s3control put-public-access-block --account-id {self.account_id} "
|
|
53
|
+
"--public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,"
|
|
54
|
+
"BlockPublicPolicy=true,RestrictPublicBuckets=true"
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
return findings
|
|
59
|
+
|
|
60
|
+
block_public_acls = public_access_config.get('BlockPublicAcls', False)
|
|
61
|
+
|
|
62
|
+
if block_public_acls:
|
|
63
|
+
findings.append(
|
|
64
|
+
self.create_finding(
|
|
65
|
+
status="PASS",
|
|
66
|
+
region="global", # S3 public access block is a global setting
|
|
67
|
+
resource_id=self.account_id,
|
|
68
|
+
checked_value="BlockPublicAcls: true",
|
|
69
|
+
actual_value="BlockPublicAcls setting is true",
|
|
70
|
+
remediation="No remediation needed"
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
findings.append(
|
|
75
|
+
self.create_finding(
|
|
76
|
+
status="FAIL",
|
|
77
|
+
region="global", # S3 public access block is a global setting
|
|
78
|
+
resource_id=self.account_id,
|
|
79
|
+
checked_value="BlockPublicAcls: true",
|
|
80
|
+
actual_value="BlockPublicAcls setting is false",
|
|
81
|
+
remediation=(
|
|
82
|
+
"Enable S3 Block Public Access at the account level using the AWS CLI command: "
|
|
83
|
+
f"aws s3control put-public-access-block --account-id {self.account_id} "
|
|
84
|
+
"--public-access-block-configuration BlockPublicAcls=true"
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return findings
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SRA-S3-03: S3 ignore public ACL is enabled.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from sraverify.services.s3.base import S3Check
|
|
6
|
+
from sraverify.core.logging import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SRA_S3_03(S3Check):
|
|
10
|
+
"""Check if S3 ignore public ACLs is enabled for the account."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
"""Initialize the check."""
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.check_id = "SRA-S3-03"
|
|
16
|
+
self.check_name = "S3 ignore public ACL is enabled"
|
|
17
|
+
self.account_type = "application"
|
|
18
|
+
self.severity = "HIGH"
|
|
19
|
+
self.description = (
|
|
20
|
+
"This check verifies whether the IgnorePublicACLs is set to True. Setting this causes Amazon S3 "
|
|
21
|
+
"to ignore all public ACLs on buckets and objects in the bucket."
|
|
22
|
+
)
|
|
23
|
+
self.check_logic = (
|
|
24
|
+
"Check if IgnorePublicAcls is set to true in the account's public access block configuration."
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
28
|
+
"""
|
|
29
|
+
Execute the check.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
List of findings
|
|
33
|
+
"""
|
|
34
|
+
findings = []
|
|
35
|
+
|
|
36
|
+
# Get public access block configuration using the base class method
|
|
37
|
+
# This will use the cache if available or make API calls if needed
|
|
38
|
+
public_access_config = self.get_public_access()
|
|
39
|
+
|
|
40
|
+
# Check if the configuration exists and IgnorePublicAcls is enabled
|
|
41
|
+
if not public_access_config:
|
|
42
|
+
findings.append(
|
|
43
|
+
self.create_finding(
|
|
44
|
+
status="FAIL",
|
|
45
|
+
region="global", # S3 public access block is a global setting
|
|
46
|
+
resource_id=self.account_id,
|
|
47
|
+
checked_value="IgnorePublicAcls: true",
|
|
48
|
+
actual_value="No public access block configuration found",
|
|
49
|
+
remediation=(
|
|
50
|
+
"Enable S3 Block Public Access at the account level using the AWS CLI command: "
|
|
51
|
+
f"aws s3control put-public-access-block --account-id {self.account_id} "
|
|
52
|
+
"--public-access-block-configuration BlockPublicAcls=true,IgnorePublicAcls=true,"
|
|
53
|
+
"BlockPublicPolicy=true,RestrictPublicBuckets=true"
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
return findings
|
|
58
|
+
|
|
59
|
+
ignore_public_acls = public_access_config.get('IgnorePublicAcls', False)
|
|
60
|
+
|
|
61
|
+
if ignore_public_acls:
|
|
62
|
+
findings.append(
|
|
63
|
+
self.create_finding(
|
|
64
|
+
status="PASS",
|
|
65
|
+
region="global", # S3 public access block is a global setting
|
|
66
|
+
resource_id=self.account_id,
|
|
67
|
+
checked_value="IgnorePublicAcls: true",
|
|
68
|
+
actual_value="IgnorePublicAcls setting is true",
|
|
69
|
+
remediation="No remediation needed"
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
else:
|
|
73
|
+
findings.append(
|
|
74
|
+
self.create_finding(
|
|
75
|
+
status="FAIL",
|
|
76
|
+
region="global", # S3 public access block is a global setting
|
|
77
|
+
resource_id=self.account_id,
|
|
78
|
+
checked_value="IgnorePublicAcls: true",
|
|
79
|
+
actual_value="IgnorePublicAcls setting is false",
|
|
80
|
+
remediation=(
|
|
81
|
+
"Enable S3 Ignore Public ACLs at the account level using the AWS CLI command: "
|
|
82
|
+
f"aws s3control put-public-access-block --account-id {self.account_id} "
|
|
83
|
+
"--public-access-block-configuration IgnorePublicAcls=true"
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return findings
|