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,220 @@
|
|
|
1
|
+
from typing import Dict, List, Any
|
|
2
|
+
from sraverify.checks import SecurityCheck
|
|
3
|
+
from botocore.exceptions import ClientError
|
|
4
|
+
|
|
5
|
+
class SRACT1(SecurityCheck):
|
|
6
|
+
"""SRA-CT-1: Organization CloudTrail Configuration"""
|
|
7
|
+
|
|
8
|
+
def __init__(self, check_type="organization"):
|
|
9
|
+
super().__init__(check_type=check_type)
|
|
10
|
+
self.check_id = "SRA-CT-1"
|
|
11
|
+
self.check_name = "Organization CloudTrail Configuration"
|
|
12
|
+
self.severity = "HIGH"
|
|
13
|
+
self.description = ('This check verifies that an organization trail is configured for your AWS Organization. '
|
|
14
|
+
'It is important to have uniform logging strategy for your AWS environment. Organization '
|
|
15
|
+
'trail logs all events for all AWS accounts in that organization and delivers logs to a '
|
|
16
|
+
'single S3 bucket, CloudWatch Logs and Event Bridge. Organization trails are automatically '
|
|
17
|
+
'applied to all member accounts in the organization. Member accounts can see the '
|
|
18
|
+
'organization trail, but can\'t modify or delete it. Organization trail should be '
|
|
19
|
+
'configured for all AWS regions even if you are not operating out of any region.')
|
|
20
|
+
self.check_logic = ('1. Verify execution from Organization Management account | ' # Step 1: Check account permissions
|
|
21
|
+
'2. List CloudTrail trails in current region | ' # Step 2: Get all trails
|
|
22
|
+
'3. Check for organization trail with IsOrganizationTrail=true and IsMultiRegionTrail=true | ' # Step 3: Verify organization trail exists
|
|
23
|
+
'4. Verify trail configuration (S3 bucket, CloudWatch Logs, EventBridge) | ' # Step 4: Check trail settings
|
|
24
|
+
'5. Check passes if organization trail is properly configured') # Step 5: All checks must pass
|
|
25
|
+
self.service = 'CloudTrail'
|
|
26
|
+
|
|
27
|
+
def get_findings(self):
|
|
28
|
+
"""Return the findings"""
|
|
29
|
+
return self.findings
|
|
30
|
+
|
|
31
|
+
def run(self, session):
|
|
32
|
+
"""Run the security check"""
|
|
33
|
+
try:
|
|
34
|
+
region = session.region_name
|
|
35
|
+
account_id = session.client('sts').get_caller_identity()['Account']
|
|
36
|
+
|
|
37
|
+
# Initialize clients
|
|
38
|
+
org_client = session.client('organizations')
|
|
39
|
+
cloudtrail_client = session.client('cloudtrail')
|
|
40
|
+
|
|
41
|
+
# Step 1: Verify we're in management account using org_mgmt_checker
|
|
42
|
+
is_management, error_message = self.org_checker.verify_org_management()
|
|
43
|
+
if not is_management:
|
|
44
|
+
finding = {
|
|
45
|
+
'CheckId': self.check_id,
|
|
46
|
+
'Status': 'ERROR',
|
|
47
|
+
'Region': region,
|
|
48
|
+
"Severity": self.severity,
|
|
49
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
50
|
+
'Description': self.description,
|
|
51
|
+
'ResourceId': account_id,
|
|
52
|
+
'ResourceType': 'AWS::Organizations::Account',
|
|
53
|
+
'AccountId': account_id,
|
|
54
|
+
'CheckedValue': 'Management Account Access',
|
|
55
|
+
'ActualValue': error_message if error_message else 'Not running from management account',
|
|
56
|
+
'Remediation': 'Run this check from the Organization Management Account',
|
|
57
|
+
'Service': self.service,
|
|
58
|
+
'CheckLogic': self.check_logic,
|
|
59
|
+
'CheckType': self.check_type
|
|
60
|
+
}
|
|
61
|
+
self.findings.append(finding)
|
|
62
|
+
return self.findings
|
|
63
|
+
|
|
64
|
+
# Steps 2 & 3: Check for organization trail
|
|
65
|
+
try:
|
|
66
|
+
trails = cloudtrail_client.list_trails()['Trails']
|
|
67
|
+
org_trail = None
|
|
68
|
+
non_org_trails = []
|
|
69
|
+
|
|
70
|
+
for trail in trails:
|
|
71
|
+
trail_info = cloudtrail_client.get_trail(Name=trail['Name'])['Trail']
|
|
72
|
+
if (trail_info.get('IsOrganizationTrail', False) and
|
|
73
|
+
trail_info.get('IsMultiRegionTrail', False)):
|
|
74
|
+
org_trail = trail_info
|
|
75
|
+
break
|
|
76
|
+
else:
|
|
77
|
+
non_org_trails.append(trail['Name'])
|
|
78
|
+
|
|
79
|
+
if not org_trail:
|
|
80
|
+
failure_details = []
|
|
81
|
+
if not trails:
|
|
82
|
+
failure_details.append("No trails found")
|
|
83
|
+
if non_org_trails:
|
|
84
|
+
failure_details.append(f"Found non-organization trails: {', '.join(non_org_trails)}")
|
|
85
|
+
|
|
86
|
+
self.findings.append({
|
|
87
|
+
'CheckId': self.check_id,
|
|
88
|
+
'Status': 'FAIL',
|
|
89
|
+
'Region': region,
|
|
90
|
+
"Severity": self.severity,
|
|
91
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
92
|
+
'Description': self.description,
|
|
93
|
+
'ResourceId': account_id,
|
|
94
|
+
'ResourceType': 'AWS::CloudTrail::Trail',
|
|
95
|
+
'AccountId': account_id,
|
|
96
|
+
'CheckedValue': 'Organization Trail Configuration',
|
|
97
|
+
'ActualValue': ' | '.join(failure_details) if failure_details else 'No organization-wide trail found',
|
|
98
|
+
'Remediation': 'Create an organization trail that logs all regions',
|
|
99
|
+
'Service': 'CloudTrail',
|
|
100
|
+
'CheckLogic': self.check_logic,
|
|
101
|
+
'CheckType': self.check_type
|
|
102
|
+
})
|
|
103
|
+
return self.findings
|
|
104
|
+
|
|
105
|
+
# Step 4: Verify trail configuration
|
|
106
|
+
try:
|
|
107
|
+
trail_status = cloudtrail_client.get_trail_status(Name=org_trail['Name'])
|
|
108
|
+
|
|
109
|
+
config_issues = []
|
|
110
|
+
|
|
111
|
+
if not org_trail.get('CloudWatchLogsLogGroupArn'):
|
|
112
|
+
config_issues.append("CloudWatch Logs integration not configured")
|
|
113
|
+
|
|
114
|
+
if not org_trail.get('S3BucketName'):
|
|
115
|
+
config_issues.append("S3 bucket not configured")
|
|
116
|
+
|
|
117
|
+
if not trail_status.get('IsLogging', False):
|
|
118
|
+
config_issues.append("Trail logging is not enabled")
|
|
119
|
+
|
|
120
|
+
if config_issues:
|
|
121
|
+
self.findings.append({
|
|
122
|
+
'CheckId': self.check_id,
|
|
123
|
+
'Status': 'FAIL',
|
|
124
|
+
'Region': region,
|
|
125
|
+
"Severity": self.severity,
|
|
126
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
127
|
+
'Description': self.description,
|
|
128
|
+
'ResourceId': org_trail['TrailARN'],
|
|
129
|
+
'ResourceType': 'AWS::CloudTrail::Trail',
|
|
130
|
+
'AccountId': account_id,
|
|
131
|
+
'CheckedValue': 'Trail Configuration Details',
|
|
132
|
+
'ActualValue': f"Configuration issues found: {' | '.join(config_issues)}",
|
|
133
|
+
'Remediation': 'Configure CloudWatch Logs, S3 bucket, and enable logging for the organization trail',
|
|
134
|
+
'Service': 'CloudTrail',
|
|
135
|
+
'CheckLogic': self.check_logic,
|
|
136
|
+
'CheckType': self.check_type
|
|
137
|
+
})
|
|
138
|
+
return self.findings
|
|
139
|
+
|
|
140
|
+
# All checks passed
|
|
141
|
+
self.findings.append({
|
|
142
|
+
'CheckId': self.check_id,
|
|
143
|
+
'Status': 'PASS',
|
|
144
|
+
'Region': region,
|
|
145
|
+
"Severity": self.severity,
|
|
146
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
147
|
+
'Description': self.description,
|
|
148
|
+
'ResourceId': org_trail['TrailARN'],
|
|
149
|
+
'ResourceType': 'AWS::CloudTrail::Trail',
|
|
150
|
+
'AccountId': account_id,
|
|
151
|
+
'CheckedValue': 'Organization Trail Configuration',
|
|
152
|
+
'ActualValue': (f"Organization trail: {org_trail['Name']} | "
|
|
153
|
+
f"Multi-region: {org_trail['IsMultiRegionTrail']} | "
|
|
154
|
+
f"Logging Enabled: {trail_status['IsLogging']} | "
|
|
155
|
+
f"S3 Bucket: {org_trail['S3BucketName']} | "
|
|
156
|
+
f"CloudWatch Logs Configured: {'Yes' if org_trail.get('CloudWatchLogsLogGroupArn') else 'No'}"),
|
|
157
|
+
'Remediation': 'None required',
|
|
158
|
+
'Service': 'CloudTrail',
|
|
159
|
+
'CheckLogic': self.check_logic,
|
|
160
|
+
'CheckType': self.check_type
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
except ClientError as e:
|
|
164
|
+
self.findings.append({
|
|
165
|
+
'CheckId': self.check_id,
|
|
166
|
+
'Status': 'ERROR',
|
|
167
|
+
'Region': region,
|
|
168
|
+
"Severity": self.severity,
|
|
169
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
170
|
+
'Description': self.description,
|
|
171
|
+
'ResourceId': org_trail['TrailARN'],
|
|
172
|
+
'ResourceType': 'AWS::CloudTrail::Trail',
|
|
173
|
+
'AccountId': account_id,
|
|
174
|
+
'CheckedValue': 'Trail Status',
|
|
175
|
+
'ActualValue': f'Error checking trail status: {str(e)} | Error Code: {e.response["Error"]["Code"]}',
|
|
176
|
+
'Remediation': 'Verify CloudTrail permissions and trail configuration',
|
|
177
|
+
'Service': 'CloudTrail',
|
|
178
|
+
'CheckLogic': self.check_logic,
|
|
179
|
+
'CheckType': self.check_type
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
except ClientError as e:
|
|
183
|
+
self.findings.append({
|
|
184
|
+
'CheckId': self.check_id,
|
|
185
|
+
'Status': 'ERROR',
|
|
186
|
+
'Region': region,
|
|
187
|
+
"Severity": self.severity,
|
|
188
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
189
|
+
'Description': self.description,
|
|
190
|
+
'ResourceId': account_id,
|
|
191
|
+
'ResourceType': 'AWS::CloudTrail::Trail',
|
|
192
|
+
'AccountId': account_id,
|
|
193
|
+
'CheckedValue': 'CloudTrail Access',
|
|
194
|
+
'ActualValue': f'Error accessing CloudTrail: {str(e)} | Error Code: {e.response["Error"]["Code"]}',
|
|
195
|
+
'Remediation': 'Verify CloudTrail permissions and service availability',
|
|
196
|
+
'Service': 'CloudTrail',
|
|
197
|
+
'CheckLogic': self.check_logic,
|
|
198
|
+
'CheckType': self.check_type
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
except Exception as e:
|
|
202
|
+
self.findings.append({
|
|
203
|
+
'CheckId': self.check_id,
|
|
204
|
+
'Status': 'ERROR',
|
|
205
|
+
'Region': region,
|
|
206
|
+
"Severity": self.severity,
|
|
207
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
208
|
+
'Description': self.description,
|
|
209
|
+
'ResourceId': account_id,
|
|
210
|
+
'ResourceType': 'AWS::CloudTrail::Trail',
|
|
211
|
+
'AccountId': account_id,
|
|
212
|
+
'CheckedValue': 'Check Execution',
|
|
213
|
+
'ActualValue': f'Unexpected error during check execution: {str(e)}',
|
|
214
|
+
'Remediation': 'Review error logs and verify AWS credentials and permissions',
|
|
215
|
+
'Service': 'CloudTrail',
|
|
216
|
+
'CheckLogic': self.check_logic,
|
|
217
|
+
'CheckType': self.check_type
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
return self.findings
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
from typing import Dict, List, Any
|
|
2
|
+
from sraverify.checks import SecurityCheck
|
|
3
|
+
from botocore.exceptions import ClientError
|
|
4
|
+
import logging
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
|
|
7
|
+
class SRACT10(SecurityCheck):
|
|
8
|
+
"""SRA-CT-10: Organization Trail Log File Validation Status"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, check_type="organization"):
|
|
11
|
+
"""Initialize the check with organization type"""
|
|
12
|
+
super().__init__(check_type=check_type)
|
|
13
|
+
self.check_id = "SRA-CT-10"
|
|
14
|
+
self.check_name = "Organization trail is configured to deliver Log file validation digest files to destination bucket"
|
|
15
|
+
self.description = ('This check verifies that log file validation digest files are being successfully delivered to a S3 bucket. '
|
|
16
|
+
'Log file validation helps ensure the integrity and authenticity of CloudTrail logs by creating digitally '
|
|
17
|
+
'signed digest files containing hashes of log files.')
|
|
18
|
+
self.service = "CloudTrail"
|
|
19
|
+
self.severity = "HIGH"
|
|
20
|
+
self.check_type = check_type
|
|
21
|
+
self.check_logic = ('1. Verify execution from Organization Management Account | '
|
|
22
|
+
'2. List CloudTrail trails in current region | '
|
|
23
|
+
'3. Check for organization trail with IsOrganizationTrail=true | '
|
|
24
|
+
'4. Verify log file validation configuration and successful delivery by checking: '
|
|
25
|
+
'a) EnableLogFileValidation is true, b) S3 bucket is configured, '
|
|
26
|
+
'c) No digest delivery errors exist, d) Latest digest delivery was successful within 24 hours')
|
|
27
|
+
self.logger = logging.getLogger(self.__class__.__name__)
|
|
28
|
+
self.findings = []
|
|
29
|
+
|
|
30
|
+
def get_findings(self) -> List[Dict[str, Any]]:
|
|
31
|
+
"""Return the findings"""
|
|
32
|
+
return self.findings
|
|
33
|
+
|
|
34
|
+
def check_digest_delivery_status(self, cloudtrail_client, trail_name: str) -> tuple:
|
|
35
|
+
"""Check digest file delivery status and return status details"""
|
|
36
|
+
try:
|
|
37
|
+
status = cloudtrail_client.get_trail_status(Name=trail_name)
|
|
38
|
+
latest_delivery_time = status.get('LatestDigestDeliveryTime')
|
|
39
|
+
latest_delivery_error = status.get('LatestDigestDeliveryError', '')
|
|
40
|
+
is_logging = status.get('IsLogging', False)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Step 1: Verify we're in management account using org_mgmt_checker
|
|
44
|
+
is_management, error_message = self.org_checker.verify_org_management()
|
|
45
|
+
if not is_management:
|
|
46
|
+
finding = {
|
|
47
|
+
'CheckId': self.check_id,
|
|
48
|
+
'Status': 'ERROR',
|
|
49
|
+
'Region': region,
|
|
50
|
+
"Severity": self.severity,
|
|
51
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
52
|
+
'Description': self.description,
|
|
53
|
+
'ResourceId': account_id,
|
|
54
|
+
'ResourceType': 'AWS::Organizations::Account',
|
|
55
|
+
'AccountId': account_id,
|
|
56
|
+
'CheckedValue': 'Management Account Access',
|
|
57
|
+
'ActualValue': error_message if error_message else 'Not running from management account',
|
|
58
|
+
'Remediation': 'Run this check from the Organization Management Account',
|
|
59
|
+
'Service': self.service,
|
|
60
|
+
'CheckLogic': self.check_logic,
|
|
61
|
+
'CheckType': self.check_type
|
|
62
|
+
}
|
|
63
|
+
self.findings.append(finding)
|
|
64
|
+
return self.findings
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Check if delivery is recent (within 24 hours)
|
|
68
|
+
current_time = datetime.now(timezone.utc)
|
|
69
|
+
is_recent = False
|
|
70
|
+
if latest_delivery_time:
|
|
71
|
+
time_difference = current_time - latest_delivery_time
|
|
72
|
+
is_recent = time_difference.total_seconds() < 86400 # 24 hours
|
|
73
|
+
|
|
74
|
+
return is_logging, is_recent, latest_delivery_error
|
|
75
|
+
|
|
76
|
+
except ClientError as e:
|
|
77
|
+
self.logger.error(f"Error getting trail status for {trail_name}: {str(e)}")
|
|
78
|
+
return False, False, str(e)
|
|
79
|
+
|
|
80
|
+
def run(self, session) -> None:
|
|
81
|
+
"""Run the security check"""
|
|
82
|
+
try:
|
|
83
|
+
# Get account information
|
|
84
|
+
sts_client = session.client('sts')
|
|
85
|
+
account_id = sts_client.get_caller_identity()['Account']
|
|
86
|
+
region = session.region_name
|
|
87
|
+
self.logger.debug(f"Running check for account: {account_id} in region: {region}")
|
|
88
|
+
|
|
89
|
+
# Initialize CloudTrail client
|
|
90
|
+
cloudtrail_client = session.client('cloudtrail')
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
# List trails and find organization trails
|
|
94
|
+
trails = cloudtrail_client.describe_trails(includeShadowTrails=True)
|
|
95
|
+
org_trails = [t for t in trails['trailList'] if t.get('IsOrganizationTrail')]
|
|
96
|
+
self.logger.debug(f"Found {len(org_trails)} organization trails")
|
|
97
|
+
|
|
98
|
+
if not org_trails:
|
|
99
|
+
self.findings.append({
|
|
100
|
+
"CheckId": self.check_id,
|
|
101
|
+
"Status": "FAIL",
|
|
102
|
+
"Region": region,
|
|
103
|
+
"Severity": self.severity,
|
|
104
|
+
"Title": f"{self.check_id} {self.check_name}",
|
|
105
|
+
"Description": self.description,
|
|
106
|
+
"ResourceId": "organization-trail",
|
|
107
|
+
"ResourceType": "AWS::CloudTrail::Trail",
|
|
108
|
+
"AccountId": account_id,
|
|
109
|
+
"CheckedValue": "Organization Trail Configuration",
|
|
110
|
+
"ActualValue": "No organization trail found",
|
|
111
|
+
"Remediation": "Create an organization trail with log file validation enabled",
|
|
112
|
+
"Service": self.service,
|
|
113
|
+
"CheckLogic": self.check_logic,
|
|
114
|
+
"CheckType": self.check_type
|
|
115
|
+
})
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
# Check each organization trail for log file validation and digest delivery
|
|
119
|
+
valid_trail = None
|
|
120
|
+
for trail in org_trails:
|
|
121
|
+
trail_name = trail['Name']
|
|
122
|
+
log_validation_enabled = trail.get('LogFileValidationEnabled', False)
|
|
123
|
+
s3_bucket = trail.get('S3BucketName')
|
|
124
|
+
|
|
125
|
+
# Skip if log validation is not enabled or S3 bucket is not configured
|
|
126
|
+
if not (log_validation_enabled and s3_bucket):
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
is_logging, is_recent, delivery_error = self.check_digest_delivery_status(cloudtrail_client, trail_name)
|
|
130
|
+
|
|
131
|
+
if is_logging and is_recent and not delivery_error:
|
|
132
|
+
valid_trail = trail
|
|
133
|
+
self.logger.debug(f"Found valid trail with successful digest delivery: {trail_name}")
|
|
134
|
+
break
|
|
135
|
+
|
|
136
|
+
# Create finding based on log validation and digest delivery status
|
|
137
|
+
if valid_trail:
|
|
138
|
+
self.findings.append({
|
|
139
|
+
"CheckId": self.check_id,
|
|
140
|
+
"Status": "PASS",
|
|
141
|
+
"Region": region,
|
|
142
|
+
"Severity": self.severity,
|
|
143
|
+
"Title": f"{self.check_id} {self.check_name}",
|
|
144
|
+
"Description": self.description,
|
|
145
|
+
"ResourceId": valid_trail['TrailARN'],
|
|
146
|
+
"ResourceType": "AWS::CloudTrail::Trail",
|
|
147
|
+
"AccountId": account_id,
|
|
148
|
+
"CheckedValue": "Log File Validation Status",
|
|
149
|
+
"ActualValue": f"Organization trail {valid_trail['Name']} is successfully delivering digest files to S3 bucket {valid_trail['S3BucketName']}",
|
|
150
|
+
"Remediation": "None required",
|
|
151
|
+
"Service": self.service,
|
|
152
|
+
"CheckLogic": self.check_logic,
|
|
153
|
+
"CheckType": self.check_type
|
|
154
|
+
})
|
|
155
|
+
else:
|
|
156
|
+
actual_value = "No organization trail found with successful recent digest file delivery"
|
|
157
|
+
remediation = "Verify log file validation configuration and S3 permissions"
|
|
158
|
+
if org_trails:
|
|
159
|
+
trail = org_trails[0]
|
|
160
|
+
if not trail.get('LogFileValidationEnabled'):
|
|
161
|
+
actual_value = f"Trail {trail['Name']} does not have log file validation enabled"
|
|
162
|
+
remediation = "Enable log file validation for the organization trail"
|
|
163
|
+
elif not trail.get('S3BucketName'):
|
|
164
|
+
actual_value = f"Trail {trail['Name']} has no S3 bucket configured"
|
|
165
|
+
remediation = "Configure S3 bucket for the organization trail"
|
|
166
|
+
elif delivery_error:
|
|
167
|
+
actual_value = f"Trail {trail['Name']} has digest delivery error: {delivery_error}"
|
|
168
|
+
remediation = "Resolve digest delivery errors (check S3 bucket permissions)"
|
|
169
|
+
elif not is_recent:
|
|
170
|
+
actual_value = f"Trail {trail['Name']} has no recent digest file delivery"
|
|
171
|
+
remediation = "Verify trail logging is enabled and check CloudWatch logs for errors"
|
|
172
|
+
|
|
173
|
+
self.findings.append({
|
|
174
|
+
"CheckId": self.check_id,
|
|
175
|
+
"Status": "FAIL",
|
|
176
|
+
"Region": region,
|
|
177
|
+
"Severity": self.severity,
|
|
178
|
+
"Title": f"{self.check_id} {self.check_name}",
|
|
179
|
+
"Description": self.description,
|
|
180
|
+
"ResourceId": (valid_trail or org_trails[0])['TrailARN'],
|
|
181
|
+
"ResourceType": "AWS::CloudTrail::Trail",
|
|
182
|
+
"AccountId": account_id,
|
|
183
|
+
"CheckedValue": "Log File Validation Status",
|
|
184
|
+
"ActualValue": actual_value,
|
|
185
|
+
"Remediation": remediation,
|
|
186
|
+
"Service": self.service,
|
|
187
|
+
"CheckLogic": self.check_logic,
|
|
188
|
+
"CheckType": self.check_type
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
except ClientError as e:
|
|
192
|
+
self.logger.error(f"Error accessing CloudTrail: {str(e)}")
|
|
193
|
+
self.findings.append({
|
|
194
|
+
"CheckId": self.check_id,
|
|
195
|
+
"Status": "ERROR",
|
|
196
|
+
"Region": region,
|
|
197
|
+
"Severity": self.severity,
|
|
198
|
+
"Title": f"{self.check_id} {self.check_name}",
|
|
199
|
+
"Description": self.description,
|
|
200
|
+
"ResourceId": "cloudtrail",
|
|
201
|
+
"ResourceType": "AWS::CloudTrail::Trail",
|
|
202
|
+
"AccountId": account_id,
|
|
203
|
+
"CheckedValue": "CloudTrail API Access",
|
|
204
|
+
"ActualValue": f"Error accessing CloudTrail: {str(e)}",
|
|
205
|
+
"Remediation": "Verify CloudTrail permissions",
|
|
206
|
+
"Service": self.service,
|
|
207
|
+
"CheckLogic": self.check_logic,
|
|
208
|
+
"CheckType": self.check_type
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
except Exception as e:
|
|
212
|
+
self.logger.error(f"Unexpected error in check: {str(e)}")
|
|
213
|
+
self.findings.append({
|
|
214
|
+
"CheckId": self.check_id,
|
|
215
|
+
"Status": "ERROR",
|
|
216
|
+
"Region": region if 'region' in locals() else session.region_name,
|
|
217
|
+
"Severity": self.severity,
|
|
218
|
+
"Title": f"{self.check_id} {self.check_name}",
|
|
219
|
+
"Description": self.description,
|
|
220
|
+
"ResourceId": "check-execution",
|
|
221
|
+
"ResourceType": "AWS::CloudTrail::Trail",
|
|
222
|
+
"AccountId": account_id if 'account_id' in locals() else "unknown",
|
|
223
|
+
"CheckedValue": "Check Execution",
|
|
224
|
+
"ActualValue": f"Unexpected error: {str(e)}",
|
|
225
|
+
"Remediation": "Contact support team",
|
|
226
|
+
"Service": self.service,
|
|
227
|
+
"CheckLogic": self.check_logic,
|
|
228
|
+
"CheckType": self.check_type
|
|
229
|
+
})
|