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,177 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SRA-CONFIG-09: AWS Config Aggregator Status.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from sraverify.services.config.base import ConfigCheck
|
|
6
|
+
from sraverify.core.logging import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SRA_CONFIG_09(ConfigCheck):
|
|
10
|
+
"""Check if Config Organization aggregator is in a valid status."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
"""Initialize the check."""
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.check_id = "SRA-CONFIG-09"
|
|
16
|
+
self.check_name = "Config Organization aggregator is in a valid status"
|
|
17
|
+
self.account_type = "audit" # This check applies to audit account
|
|
18
|
+
self.severity = "MEDIUM"
|
|
19
|
+
self.description = (
|
|
20
|
+
"This check verifies whether Config Organization aggregator has a valid status. "
|
|
21
|
+
"A value of FAILED indicates errors while moving data and value OUTDATED indicates "
|
|
22
|
+
"the data is not the most recent."
|
|
23
|
+
)
|
|
24
|
+
self.check_logic = (
|
|
25
|
+
"Checks if all aggregator sources have a status of SUCCEEDED using "
|
|
26
|
+
"describe-configuration-aggregator-sources-status API."
|
|
27
|
+
)
|
|
28
|
+
self.resource_type = "AWS::Config::ConfigurationAggregator"
|
|
29
|
+
|
|
30
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
31
|
+
"""
|
|
32
|
+
Execute the check.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
List of findings
|
|
36
|
+
"""
|
|
37
|
+
findings = []
|
|
38
|
+
|
|
39
|
+
if not self.regions:
|
|
40
|
+
findings.append(
|
|
41
|
+
self.create_finding(
|
|
42
|
+
status="ERROR",
|
|
43
|
+
region="global",
|
|
44
|
+
resource_id="config:global",
|
|
45
|
+
checked_value="All source statuses are SUCCEEDED",
|
|
46
|
+
actual_value="No regions specified for check",
|
|
47
|
+
remediation="Specify at least one region when running the check"
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
return findings
|
|
51
|
+
|
|
52
|
+
# First, find an organization aggregator in any region
|
|
53
|
+
found_org_aggregator = False
|
|
54
|
+
org_aggregator_region = None
|
|
55
|
+
org_aggregator_name = None
|
|
56
|
+
org_aggregator_arn = None
|
|
57
|
+
|
|
58
|
+
for region in self.regions:
|
|
59
|
+
# Get configuration aggregators for the region using the cache
|
|
60
|
+
aggregators = self.get_configuration_aggregators(region)
|
|
61
|
+
|
|
62
|
+
# Check if any of the aggregators is an organization aggregator
|
|
63
|
+
for aggregator in aggregators:
|
|
64
|
+
if 'OrganizationAggregationSource' in aggregator:
|
|
65
|
+
found_org_aggregator = True
|
|
66
|
+
org_aggregator_region = region
|
|
67
|
+
org_aggregator_name = aggregator.get('ConfigurationAggregatorName', 'Unknown')
|
|
68
|
+
org_aggregator_arn = aggregator.get('ConfigurationAggregatorArn',
|
|
69
|
+
f"arn:aws:config:{region}:{self.account_id}:config-aggregator/{org_aggregator_name}")
|
|
70
|
+
break
|
|
71
|
+
|
|
72
|
+
if found_org_aggregator:
|
|
73
|
+
break
|
|
74
|
+
|
|
75
|
+
# If no organization aggregator found in any region, return a global failure
|
|
76
|
+
if not found_org_aggregator:
|
|
77
|
+
findings.append(
|
|
78
|
+
self.create_finding(
|
|
79
|
+
status="FAIL",
|
|
80
|
+
region="global",
|
|
81
|
+
resource_id=f"arn:aws:config:global:{self.account_id}:config-aggregator/none",
|
|
82
|
+
checked_value="All source statuses are SUCCEEDED",
|
|
83
|
+
actual_value="No organization aggregator found in any region",
|
|
84
|
+
remediation=(
|
|
85
|
+
"Create an organization configuration aggregator in at least one region using: aws configservice put-configuration-aggregator "
|
|
86
|
+
"--configuration-aggregator-name organization-aggregator --organization-aggregation-source "
|
|
87
|
+
f"\"EnableAllRegions=true,RoleArn=arn:aws:iam::{self.account_id}:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfigServiceRole\" "
|
|
88
|
+
"--region <region>"
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
return findings
|
|
93
|
+
|
|
94
|
+
# Get client for the region where we found the organization aggregator
|
|
95
|
+
client = self.get_client(org_aggregator_region)
|
|
96
|
+
if not client:
|
|
97
|
+
findings.append(
|
|
98
|
+
self.create_finding(
|
|
99
|
+
status="ERROR",
|
|
100
|
+
region="global",
|
|
101
|
+
resource_id=org_aggregator_arn,
|
|
102
|
+
checked_value="All source statuses are SUCCEEDED",
|
|
103
|
+
actual_value=f"No Config client available for region {org_aggregator_region}",
|
|
104
|
+
remediation="Ensure AWS Config service is available in the region and you have proper permissions"
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
return findings
|
|
108
|
+
|
|
109
|
+
# Get aggregator sources status
|
|
110
|
+
try:
|
|
111
|
+
source_statuses = client.describe_configuration_aggregator_sources_status(org_aggregator_name)
|
|
112
|
+
except Exception as e:
|
|
113
|
+
findings.append(
|
|
114
|
+
self.create_finding(
|
|
115
|
+
status="ERROR",
|
|
116
|
+
region="global",
|
|
117
|
+
resource_id=org_aggregator_arn,
|
|
118
|
+
checked_value="All source statuses are SUCCEEDED",
|
|
119
|
+
actual_value=f"Error getting source statuses for aggregator '{org_aggregator_name}' in region {org_aggregator_region}: {str(e)}",
|
|
120
|
+
remediation="Ensure you have proper permissions to check aggregator status"
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
return findings
|
|
124
|
+
|
|
125
|
+
if not source_statuses:
|
|
126
|
+
# No source statuses found
|
|
127
|
+
findings.append(
|
|
128
|
+
self.create_finding(
|
|
129
|
+
status="FAIL",
|
|
130
|
+
region="global",
|
|
131
|
+
resource_id=org_aggregator_arn,
|
|
132
|
+
checked_value="All source statuses are SUCCEEDED",
|
|
133
|
+
actual_value=f"No source statuses found for organization aggregator '{org_aggregator_name}' in region {org_aggregator_region}",
|
|
134
|
+
remediation=f"Check the aggregator configuration in {org_aggregator_region} and ensure it has valid sources"
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
return findings
|
|
138
|
+
|
|
139
|
+
# Check if all sources have SUCCEEDED status
|
|
140
|
+
failed_sources = []
|
|
141
|
+
for source in source_statuses:
|
|
142
|
+
source_id = source.get('SourceId', 'Unknown')
|
|
143
|
+
source_type = source.get('SourceType', 'Unknown')
|
|
144
|
+
source_status = source.get('LastUpdateStatus', 'UNKNOWN')
|
|
145
|
+
|
|
146
|
+
if source_status != "SUCCEEDED":
|
|
147
|
+
failed_sources.append(f"{source_type}:{source_id} ({source_status})")
|
|
148
|
+
|
|
149
|
+
if failed_sources:
|
|
150
|
+
# Some sources have failed
|
|
151
|
+
findings.append(
|
|
152
|
+
self.create_finding(
|
|
153
|
+
status="FAIL",
|
|
154
|
+
region="global",
|
|
155
|
+
resource_id=org_aggregator_arn,
|
|
156
|
+
checked_value="All source statuses are SUCCEEDED",
|
|
157
|
+
actual_value=f"Organization aggregator '{org_aggregator_name}' in region {org_aggregator_region} has sources with status: {', '.join(failed_sources)}",
|
|
158
|
+
remediation=(
|
|
159
|
+
f"Check the AWS Config logs for errors related to the aggregator sources. "
|
|
160
|
+
f"Ensure the aggregator has the necessary permissions to access the source accounts."
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
)
|
|
164
|
+
else:
|
|
165
|
+
# All sources have succeeded
|
|
166
|
+
findings.append(
|
|
167
|
+
self.create_finding(
|
|
168
|
+
status="PASS",
|
|
169
|
+
region="global",
|
|
170
|
+
resource_id=org_aggregator_arn,
|
|
171
|
+
checked_value="All source statuses are SUCCEEDED",
|
|
172
|
+
actual_value=f"Organization aggregator '{org_aggregator_name}' in region {org_aggregator_region} has all sources with status SUCCEEDED",
|
|
173
|
+
remediation="No remediation needed"
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
return findings
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS Config client for interacting with AWS Config 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 ConfigClient:
|
|
11
|
+
"""Client for interacting with AWS Config service."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, region: str, session: Optional[boto3.Session] = None):
|
|
14
|
+
"""
|
|
15
|
+
Initialize Config 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('config', region_name=region)
|
|
24
|
+
self.org_client = self.session.client('organizations', region_name=region)
|
|
25
|
+
self.s3_client = self.session.client('s3', region_name=region)
|
|
26
|
+
|
|
27
|
+
def get_account_id(self) -> Optional[str]:
|
|
28
|
+
"""
|
|
29
|
+
Get the current account ID.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Current account ID or None if not available
|
|
33
|
+
"""
|
|
34
|
+
try:
|
|
35
|
+
logger.debug(f"Getting current account ID in {self.region}")
|
|
36
|
+
sts_client = self.session.client("sts")
|
|
37
|
+
response = sts_client.get_caller_identity()
|
|
38
|
+
account_id = response["Account"]
|
|
39
|
+
logger.debug(f"Current account ID: {account_id}")
|
|
40
|
+
return account_id
|
|
41
|
+
except ClientError as e:
|
|
42
|
+
logger.error(f"Error getting current account ID: {e}")
|
|
43
|
+
return None
|
|
44
|
+
except Exception as e:
|
|
45
|
+
logger.error(f"Unexpected error getting current account ID: {e}")
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
def get_management_account_id(self) -> Optional[str]:
|
|
49
|
+
"""
|
|
50
|
+
Get the management account ID for the organization.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Management account ID or None if not available
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
logger.debug(f"Getting management account ID in {self.region}")
|
|
57
|
+
response = self.org_client.describe_organization()
|
|
58
|
+
management_account_id = response["Organization"]["MasterAccountId"]
|
|
59
|
+
logger.debug(f"Management account ID: {management_account_id}")
|
|
60
|
+
return management_account_id
|
|
61
|
+
except ClientError as e:
|
|
62
|
+
logger.error(f"Error getting management account ID: {e}")
|
|
63
|
+
return None
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger.error(f"Unexpected error getting management account ID: {e}")
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
def describe_configuration_recorders(self) -> List[Dict[str, Any]]:
|
|
69
|
+
"""
|
|
70
|
+
Describe configuration recorders in the current region.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
List of configuration recorders
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
logger.debug(f"Describing configuration recorders in {self.region}")
|
|
77
|
+
response = self.client.describe_configuration_recorders()
|
|
78
|
+
recorders = response.get('ConfigurationRecorders', [])
|
|
79
|
+
logger.debug(f"Found {len(recorders)} configuration recorders in {self.region}")
|
|
80
|
+
return recorders
|
|
81
|
+
except ClientError as e:
|
|
82
|
+
logger.error(f"Error describing configuration recorders in {self.region}: {e}")
|
|
83
|
+
return []
|
|
84
|
+
except Exception as e:
|
|
85
|
+
logger.error(f"Unexpected error describing configuration recorders in {self.region}: {e}")
|
|
86
|
+
return []
|
|
87
|
+
|
|
88
|
+
def describe_configuration_recorder_status(self) -> List[Dict[str, Any]]:
|
|
89
|
+
"""
|
|
90
|
+
Describe configuration recorder status in the current region.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
List of configuration recorder statuses
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
logger.debug(f"Describing configuration recorder status in {self.region}")
|
|
97
|
+
response = self.client.describe_configuration_recorder_status()
|
|
98
|
+
statuses = response.get('ConfigurationRecordersStatus', [])
|
|
99
|
+
logger.debug(f"Found {len(statuses)} configuration recorder statuses in {self.region}")
|
|
100
|
+
return statuses
|
|
101
|
+
except ClientError as e:
|
|
102
|
+
logger.error(f"Error describing configuration recorder status in {self.region}: {e}")
|
|
103
|
+
return []
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.error(f"Unexpected error describing configuration recorder status in {self.region}: {e}")
|
|
106
|
+
return []
|
|
107
|
+
|
|
108
|
+
def describe_delivery_channels(self) -> List[Dict[str, Any]]:
|
|
109
|
+
"""
|
|
110
|
+
Describe delivery channels in the current region.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
List of delivery channels
|
|
114
|
+
"""
|
|
115
|
+
try:
|
|
116
|
+
logger.debug(f"Describing delivery channels in {self.region}")
|
|
117
|
+
response = self.client.describe_delivery_channels()
|
|
118
|
+
channels = response.get('DeliveryChannels', [])
|
|
119
|
+
logger.debug(f"Found {len(channels)} delivery channels in {self.region}")
|
|
120
|
+
return channels
|
|
121
|
+
except ClientError as e:
|
|
122
|
+
logger.error(f"Error describing delivery channels in {self.region}: {e}")
|
|
123
|
+
return []
|
|
124
|
+
except Exception as e:
|
|
125
|
+
logger.error(f"Unexpected error describing delivery channels in {self.region}: {e}")
|
|
126
|
+
return []
|
|
127
|
+
|
|
128
|
+
def describe_delivery_channel_status(self) -> List[Dict[str, Any]]:
|
|
129
|
+
"""
|
|
130
|
+
Describe delivery channel status in the current region.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
List of delivery channel statuses
|
|
134
|
+
"""
|
|
135
|
+
try:
|
|
136
|
+
logger.debug(f"Describing delivery channel status in {self.region}")
|
|
137
|
+
response = self.client.describe_delivery_channel_status()
|
|
138
|
+
statuses = response.get('DeliveryChannelsStatus', [])
|
|
139
|
+
logger.debug(f"Found {len(statuses)} delivery channel statuses in {self.region}")
|
|
140
|
+
return statuses
|
|
141
|
+
except ClientError as e:
|
|
142
|
+
logger.error(f"Error describing delivery channel status in {self.region}: {e}")
|
|
143
|
+
return []
|
|
144
|
+
except Exception as e:
|
|
145
|
+
logger.error(f"Unexpected error describing delivery channel status in {self.region}: {e}")
|
|
146
|
+
return []
|
|
147
|
+
|
|
148
|
+
def describe_configuration_aggregators(self) -> List[Dict[str, Any]]:
|
|
149
|
+
"""
|
|
150
|
+
Describe configuration aggregators in the current region.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
List of configuration aggregators
|
|
154
|
+
"""
|
|
155
|
+
try:
|
|
156
|
+
logger.debug(f"Describing configuration aggregators in {self.region}")
|
|
157
|
+
response = self.client.describe_configuration_aggregators()
|
|
158
|
+
aggregators = response.get('ConfigurationAggregators', [])
|
|
159
|
+
logger.debug(f"Found {len(aggregators)} configuration aggregators in {self.region}")
|
|
160
|
+
return aggregators
|
|
161
|
+
except ClientError as e:
|
|
162
|
+
logger.error(f"Error describing configuration aggregators in {self.region}: {e}")
|
|
163
|
+
return []
|
|
164
|
+
except Exception as e:
|
|
165
|
+
logger.error(f"Unexpected error describing configuration aggregators in {self.region}: {e}")
|
|
166
|
+
return []
|
|
167
|
+
|
|
168
|
+
def describe_configuration_aggregator_sources_status(self, aggregator_name: str) -> List[Dict[str, Any]]:
|
|
169
|
+
"""
|
|
170
|
+
Describe configuration aggregator sources status in the current region.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
aggregator_name: Name of the configuration aggregator
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
List of configuration aggregator sources statuses
|
|
177
|
+
"""
|
|
178
|
+
try:
|
|
179
|
+
logger.debug(f"Describing configuration aggregator sources status for {aggregator_name} in {self.region}")
|
|
180
|
+
response = self.client.describe_configuration_aggregator_sources_status(
|
|
181
|
+
ConfigurationAggregatorName=aggregator_name
|
|
182
|
+
)
|
|
183
|
+
source_statuses = response.get('AggregatedSourceStatusList', [])
|
|
184
|
+
logger.debug(f"Found {len(source_statuses)} source statuses for aggregator {aggregator_name} in {self.region}")
|
|
185
|
+
return source_statuses
|
|
186
|
+
except ClientError as e:
|
|
187
|
+
logger.error(f"Error describing configuration aggregator sources status in {self.region}: {e}")
|
|
188
|
+
return []
|
|
189
|
+
except Exception as e:
|
|
190
|
+
logger.error(f"Unexpected error describing configuration aggregator sources status in {self.region}: {e}")
|
|
191
|
+
return []
|
|
192
|
+
|
|
193
|
+
def get_bucket_location(self, bucket_name: str) -> Optional[str]:
|
|
194
|
+
"""
|
|
195
|
+
Get the location of an S3 bucket.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
bucket_name: Name of the S3 bucket
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
Region of the S3 bucket or None if not available
|
|
202
|
+
"""
|
|
203
|
+
try:
|
|
204
|
+
logger.debug(f"Getting location for bucket {bucket_name}")
|
|
205
|
+
response = self.s3_client.get_bucket_location(Bucket=bucket_name)
|
|
206
|
+
location = response.get('LocationConstraint')
|
|
207
|
+
# If location is None, the bucket is in us-east-1
|
|
208
|
+
bucket_region = location if location else 'us-east-1'
|
|
209
|
+
logger.debug(f"Bucket {bucket_name} is in region {bucket_region}")
|
|
210
|
+
return bucket_region
|
|
211
|
+
except ClientError as e:
|
|
212
|
+
logger.error(f"Error getting bucket location for {bucket_name}: {e}")
|
|
213
|
+
return None
|
|
214
|
+
except Exception as e:
|
|
215
|
+
logger.error(f"Unexpected error getting bucket location for {bucket_name}: {e}")
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
def get_bucket_policy(self, bucket_name: str) -> Optional[Dict[str, Any]]:
|
|
219
|
+
"""
|
|
220
|
+
Get the policy of an S3 bucket.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
bucket_name: Name of the S3 bucket
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Policy of the S3 bucket or None if not available
|
|
227
|
+
"""
|
|
228
|
+
try:
|
|
229
|
+
logger.debug(f"Getting policy for bucket {bucket_name}")
|
|
230
|
+
response = self.s3_client.get_bucket_policy(Bucket=bucket_name)
|
|
231
|
+
policy = response.get('Policy')
|
|
232
|
+
logger.debug(f"Got policy for bucket {bucket_name}")
|
|
233
|
+
return policy
|
|
234
|
+
except ClientError as e:
|
|
235
|
+
logger.error(f"Error getting bucket policy for {bucket_name}: {e}")
|
|
236
|
+
return None
|
|
237
|
+
except Exception as e:
|
|
238
|
+
logger.error(f"Unexpected error getting bucket policy for {bucket_name}: {e}")
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
def list_delegated_administrators(self, service_principal: str = "config.amazonaws.com") -> List[Dict[str, Any]]:
|
|
242
|
+
"""
|
|
243
|
+
List delegated administrators for a specific service principal.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
service_principal: Service principal to check for delegated administrators
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
List of delegated administrators
|
|
250
|
+
"""
|
|
251
|
+
try:
|
|
252
|
+
logger.debug(f"Listing delegated administrators for {service_principal} in {self.region}")
|
|
253
|
+
response = self.org_client.list_delegated_administrators(ServicePrincipal=service_principal)
|
|
254
|
+
delegated_admins = response.get('DelegatedAdministrators', [])
|
|
255
|
+
logger.debug(f"Found {len(delegated_admins)} delegated administrators for {service_principal}")
|
|
256
|
+
for admin in delegated_admins:
|
|
257
|
+
logger.debug(f"Delegated admin: {admin.get('Id')} - {admin.get('Name')}")
|
|
258
|
+
return delegated_admins
|
|
259
|
+
except ClientError as e:
|
|
260
|
+
logger.error(f"Error listing delegated administrators for {service_principal}: {e}")
|
|
261
|
+
return []
|
|
262
|
+
except Exception as e:
|
|
263
|
+
logger.error(f"Unexpected error listing delegated administrators: {e}")
|
|
264
|
+
return []
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base class for EC2 security checks.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Dict, Optional, Any
|
|
5
|
+
from sraverify.core.check import SecurityCheck
|
|
6
|
+
from sraverify.services.ec2.client import EC2Client
|
|
7
|
+
from sraverify.core.logging import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EC2Check(SecurityCheck):
|
|
11
|
+
"""Base class for all EC2 security checks."""
|
|
12
|
+
|
|
13
|
+
# Class-level caches shared across all instances
|
|
14
|
+
_ebs_encryption_default_cache = {}
|
|
15
|
+
|
|
16
|
+
def __init__(self):
|
|
17
|
+
"""Initialize EC2 base check."""
|
|
18
|
+
super().__init__(
|
|
19
|
+
account_type="application",
|
|
20
|
+
service="EC2",
|
|
21
|
+
resource_type="AWS::EC2::Instance"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def _setup_clients(self):
|
|
25
|
+
"""Set up EC2 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] = EC2Client(region, session=self.session)
|
|
32
|
+
|
|
33
|
+
def get_client(self, region: str) -> Optional[EC2Client]:
|
|
34
|
+
"""
|
|
35
|
+
Get EC2 client for a specific region.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
region: AWS region name
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
EC2Client for the region or None if not available
|
|
42
|
+
"""
|
|
43
|
+
return self._clients.get(region)
|
|
44
|
+
|
|
45
|
+
def get_ebs_encryption_by_default(self, region: str) -> Dict[str, Any]:
|
|
46
|
+
"""
|
|
47
|
+
Get the EBS encryption by default status for the account in the region with caching.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
region: AWS region name
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Dictionary containing EBS encryption by default status
|
|
54
|
+
"""
|
|
55
|
+
# Check cache first
|
|
56
|
+
account_id = self.account_id
|
|
57
|
+
cache_key = f"{account_id}:{region}"
|
|
58
|
+
|
|
59
|
+
if cache_key in self.__class__._ebs_encryption_default_cache:
|
|
60
|
+
logger.debug(f"Using cached EBS encryption by default status for {region}")
|
|
61
|
+
return self.__class__._ebs_encryption_default_cache[cache_key]
|
|
62
|
+
|
|
63
|
+
client = self.get_client(region)
|
|
64
|
+
if not client:
|
|
65
|
+
logger.warning(f"No EC2 client available for region {region}")
|
|
66
|
+
return {}
|
|
67
|
+
|
|
68
|
+
# Get EBS encryption by default status from client
|
|
69
|
+
encryption_status = client.get_ebs_encryption_by_default()
|
|
70
|
+
|
|
71
|
+
# Cache the result
|
|
72
|
+
self.__class__._ebs_encryption_default_cache[cache_key] = encryption_status
|
|
73
|
+
logger.debug(f"Cached EBS encryption by default status for {region}")
|
|
74
|
+
|
|
75
|
+
return encryption_status
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""EC2 security checks."""
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SRA-EC2-01: AWS account level EBS encryption by default is enabled.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from sraverify.services.ec2.base import EC2Check
|
|
6
|
+
from sraverify.core.logging import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SRA_EC2_01(EC2Check):
|
|
10
|
+
"""Check if AWS account level EBS encryption by default is enabled."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
"""Initialize the check."""
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.check_id = "SRA-EC2-01"
|
|
16
|
+
self.check_name = "AWS account level EBS encryption by default is enabled"
|
|
17
|
+
self.description = (
|
|
18
|
+
"This check verifies that the AWS account level configuration to encrypt EBS volumes by default "
|
|
19
|
+
"is enabled in the AWS Region. This enforces, at AWS account level, the encryption of the new EBS "
|
|
20
|
+
"volumes and snapshot copies that you create. You can use AWS managed keys or a customer managed KMS key."
|
|
21
|
+
)
|
|
22
|
+
self.severity = "HIGH"
|
|
23
|
+
self.account_type = "application"
|
|
24
|
+
self.check_logic = "Check Pass if 'EbsEncryptionByDefault' = true."
|
|
25
|
+
self.resource_type = "AWS::EC2::Volume"
|
|
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
|
+
for region in self.regions:
|
|
37
|
+
# Get EBS encryption by default status using the base class method with caching
|
|
38
|
+
encryption_status = self.get_ebs_encryption_by_default(region)
|
|
39
|
+
|
|
40
|
+
# Check if the API call was successful
|
|
41
|
+
if not encryption_status:
|
|
42
|
+
findings.append(
|
|
43
|
+
self.create_finding(
|
|
44
|
+
status="ERROR",
|
|
45
|
+
region=region,
|
|
46
|
+
resource_id=f"account/{self.account_id}/region/{region}",
|
|
47
|
+
checked_value="EbsEncryptionByDefault: true",
|
|
48
|
+
actual_value="Failed to retrieve EBS encryption by default status",
|
|
49
|
+
remediation="Ensure you have the necessary permissions to call the EC2 GetEbsEncryptionByDefault API"
|
|
50
|
+
)
|
|
51
|
+
)
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
# Check if EBS encryption by default is enabled
|
|
55
|
+
is_encryption_enabled = encryption_status.get('EbsEncryptionByDefault', False)
|
|
56
|
+
|
|
57
|
+
if is_encryption_enabled:
|
|
58
|
+
findings.append(
|
|
59
|
+
self.create_finding(
|
|
60
|
+
status="PASS",
|
|
61
|
+
region=region,
|
|
62
|
+
resource_id=f"account/{self.account_id}/region/{region}",
|
|
63
|
+
checked_value="EbsEncryptionByDefault: true",
|
|
64
|
+
actual_value=f"EBS encryption by default is enabled in region {region}",
|
|
65
|
+
remediation="No remediation needed"
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
else:
|
|
69
|
+
findings.append(
|
|
70
|
+
self.create_finding(
|
|
71
|
+
status="FAIL",
|
|
72
|
+
region=region,
|
|
73
|
+
resource_id=f"account/{self.account_id}/region/{region}",
|
|
74
|
+
checked_value="EbsEncryptionByDefault: true",
|
|
75
|
+
actual_value=f"EBS encryption by default is not enabled in region {region}",
|
|
76
|
+
remediation=(
|
|
77
|
+
f"Enable EBS encryption by default in region {region} using the AWS CLI command: "
|
|
78
|
+
f"aws ec2 enable-ebs-encryption-by-default --region {region}"
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return findings
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EC2 client for interacting with AWS EC2 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 EC2Client:
|
|
11
|
+
"""Client for interacting with AWS EC2 service."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, region: str, session: Optional[boto3.Session] = None):
|
|
14
|
+
"""
|
|
15
|
+
Initialize EC2 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('ec2', region_name=region)
|
|
24
|
+
|
|
25
|
+
def get_ebs_encryption_by_default(self) -> Dict[str, Any]:
|
|
26
|
+
"""
|
|
27
|
+
Get the EBS encryption by default status for the account in the region.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Dictionary containing EBS encryption by default status
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
logger.debug(f"Getting EBS encryption by default status in {self.region}")
|
|
34
|
+
response = self.client.get_ebs_encryption_by_default()
|
|
35
|
+
logger.debug(f"EBS encryption by default status in {self.region}: {response}")
|
|
36
|
+
return response
|
|
37
|
+
except ClientError as e:
|
|
38
|
+
logger.error(f"Error getting EBS encryption by default status in {self.region}: {e}")
|
|
39
|
+
return {}
|
|
40
|
+
except Exception as e:
|
|
41
|
+
logger.error(f"Unexpected error getting EBS encryption by default status in {self.region}: {e}")
|
|
42
|
+
return {}
|
|
43
|
+
|
|
44
|
+
def get_account_id(self) -> Optional[str]:
|
|
45
|
+
"""
|
|
46
|
+
Get the current account ID.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Current account ID or None if not available
|
|
50
|
+
"""
|
|
51
|
+
try:
|
|
52
|
+
logger.debug(f"Getting current account ID in {self.region}")
|
|
53
|
+
sts_client = self.session.client("sts")
|
|
54
|
+
response = sts_client.get_caller_identity()
|
|
55
|
+
account_id = response["Account"]
|
|
56
|
+
logger.debug(f"Current account ID: {account_id}")
|
|
57
|
+
return account_id
|
|
58
|
+
except ClientError as e:
|
|
59
|
+
logger.error(f"Error getting current account ID: {e}")
|
|
60
|
+
return None
|
|
61
|
+
except Exception as e:
|
|
62
|
+
logger.error(f"Unexpected error getting current account ID: {e}")
|
|
63
|
+
return None
|