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,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SRA-CLOUDTRAIL-08: Organization CloudTrail S3 Delivery.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from datetime import datetime, timedelta, timezone
|
|
6
|
+
from sraverify.services.cloudtrail.base import CloudTrailCheck
|
|
7
|
+
from sraverify.core.logging import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SRA_CLOUDTRAIL_08(CloudTrailCheck):
|
|
11
|
+
"""Check if organization trails are publishing logs to destination S3 bucket."""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
"""Initialize the check."""
|
|
15
|
+
super().__init__()
|
|
16
|
+
self.check_id = "SRA-CLOUDTRAIL-08"
|
|
17
|
+
self.check_name = "Organization trail is publishing logs to destination S3 bucket"
|
|
18
|
+
self.account_type = "management"
|
|
19
|
+
self.severity = "HIGH"
|
|
20
|
+
self.description = (
|
|
21
|
+
"This check verifies that last attempt to send CloudTrail logs to S3 bucket was successful. "
|
|
22
|
+
"CloudTrail log files are an audit log of actions taken by an IAM identity or an AWS service. "
|
|
23
|
+
"The integrity, completeness and availability of these logs is crucial for forensic and auditing purposes. "
|
|
24
|
+
"By logging to a dedicated and centralized Amazon S3 bucket, you can enforce strict security controls, "
|
|
25
|
+
"access, and segregation of duties."
|
|
26
|
+
)
|
|
27
|
+
self.check_logic = (
|
|
28
|
+
"Check if organization trails have LatestDeliveryTime within the last 24 hours."
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
32
|
+
"""
|
|
33
|
+
Execute the check.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
List of findings
|
|
37
|
+
"""
|
|
38
|
+
findings = []
|
|
39
|
+
|
|
40
|
+
# Get organization trails
|
|
41
|
+
org_trails = self.get_organization_trails()
|
|
42
|
+
|
|
43
|
+
if not org_trails:
|
|
44
|
+
findings.append(
|
|
45
|
+
self.create_finding(
|
|
46
|
+
status="FAIL",
|
|
47
|
+
region="global",
|
|
48
|
+
resource_id=f"organization/{self.account_id}",
|
|
49
|
+
checked_value="LatestDeliveryTime: within last 24 hours",
|
|
50
|
+
actual_value="No organization trails found",
|
|
51
|
+
remediation=(
|
|
52
|
+
"Create an organization trail in the management account using the AWS CLI command: "
|
|
53
|
+
f"aws cloudtrail create-trail --name org-trail --is-organization-trail --s3-bucket-name cloudtrail-logs-{self.account_id} "
|
|
54
|
+
f"--is-multi-region-trail --region {self.regions[0] if self.regions else 'us-east-1'} && "
|
|
55
|
+
f"aws cloudtrail start-logging --name org-trail --region {self.regions[0] if self.regions else 'us-east-1'}"
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
return findings
|
|
60
|
+
|
|
61
|
+
# Check each organization trail for S3 delivery
|
|
62
|
+
for trail in org_trails:
|
|
63
|
+
trail_name = trail.get('Name', 'Unknown')
|
|
64
|
+
trail_arn = trail.get('TrailARN', 'Unknown')
|
|
65
|
+
home_region = trail.get('HomeRegion', 'Unknown')
|
|
66
|
+
s3_bucket_name = trail.get('S3BucketName', 'Unknown')
|
|
67
|
+
|
|
68
|
+
# Get trail status to check S3 delivery
|
|
69
|
+
trail_status = self.get_trail_status(home_region, trail_arn)
|
|
70
|
+
latest_delivery_time_str = trail_status.get('LatestDeliveryTime', None)
|
|
71
|
+
latest_delivery_error = trail_status.get('LatestDeliveryError', None)
|
|
72
|
+
|
|
73
|
+
# Check if delivery time exists and is within the last 24 hours
|
|
74
|
+
if latest_delivery_time_str:
|
|
75
|
+
try:
|
|
76
|
+
# Convert string to datetime object
|
|
77
|
+
if isinstance(latest_delivery_time_str, str):
|
|
78
|
+
latest_delivery_time = datetime.fromisoformat(latest_delivery_time_str.replace('Z', '+00:00'))
|
|
79
|
+
else:
|
|
80
|
+
# Assume it's already a datetime object
|
|
81
|
+
latest_delivery_time = latest_delivery_time_str
|
|
82
|
+
|
|
83
|
+
# Get current time in UTC
|
|
84
|
+
now = datetime.now(timezone.utc)
|
|
85
|
+
|
|
86
|
+
# Check if delivery was within the last 24 hours
|
|
87
|
+
if now - latest_delivery_time < timedelta(hours=24):
|
|
88
|
+
# Trail is delivering logs to S3 within the last 24 hours
|
|
89
|
+
findings.append(
|
|
90
|
+
self.create_finding(
|
|
91
|
+
status="PASS",
|
|
92
|
+
region="global",
|
|
93
|
+
resource_id=trail_arn,
|
|
94
|
+
checked_value="LatestDeliveryTime: within last 24 hours",
|
|
95
|
+
actual_value=f"Organization trail '{trail_name}' is publishing logs to S3 bucket '{s3_bucket_name}', latest delivery time: {latest_delivery_time_str}",
|
|
96
|
+
remediation="No remediation needed"
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
# Trail has not delivered logs to S3 within the last 24 hours
|
|
101
|
+
findings.append(
|
|
102
|
+
self.create_finding(
|
|
103
|
+
status="FAIL",
|
|
104
|
+
region="global",
|
|
105
|
+
resource_id=trail_arn,
|
|
106
|
+
checked_value="LatestDeliveryTime: within last 24 hours",
|
|
107
|
+
actual_value=f"Organization trail '{trail_name}' has not published logs to S3 bucket '{s3_bucket_name}' within the last 24 hours, latest delivery time: {latest_delivery_time_str}",
|
|
108
|
+
remediation=(
|
|
109
|
+
f"Check the CloudTrail configuration and S3 bucket permissions. Ensure the trail is active using: "
|
|
110
|
+
f"aws cloudtrail start-logging --name {trail_name} --region {home_region}"
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
except (ValueError, TypeError) as e:
|
|
115
|
+
# Error parsing delivery time
|
|
116
|
+
findings.append(
|
|
117
|
+
self.create_finding(
|
|
118
|
+
status="FAIL",
|
|
119
|
+
region="global",
|
|
120
|
+
resource_id=trail_arn,
|
|
121
|
+
checked_value="LatestDeliveryTime: within last 24 hours",
|
|
122
|
+
actual_value=f"Organization trail '{trail_name}' has an invalid delivery time format: {latest_delivery_time_str}, error: {str(e)}",
|
|
123
|
+
remediation=(
|
|
124
|
+
f"Check the CloudTrail configuration and S3 bucket permissions. Ensure the trail is active using: "
|
|
125
|
+
f"aws cloudtrail start-logging --name {trail_name} --region {home_region}"
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
# No delivery time found
|
|
131
|
+
findings.append(
|
|
132
|
+
self.create_finding(
|
|
133
|
+
status="FAIL",
|
|
134
|
+
region="global",
|
|
135
|
+
resource_id=trail_arn,
|
|
136
|
+
checked_value="LatestDeliveryTime: within last 24 hours",
|
|
137
|
+
actual_value=f"Organization trail '{trail_name}' has no record of delivering logs to S3 bucket '{s3_bucket_name}'",
|
|
138
|
+
remediation=(
|
|
139
|
+
f"Check the CloudTrail configuration and S3 bucket permissions. Ensure the trail is active using: "
|
|
140
|
+
f"aws cloudtrail start-logging --name {trail_name} --region {home_region}"
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return findings
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SRA-CLOUDTRAIL-09: Organization CloudTrail CloudWatch Logs Delivery.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from datetime import datetime, timedelta, timezone
|
|
6
|
+
from sraverify.services.cloudtrail.base import CloudTrailCheck
|
|
7
|
+
from sraverify.core.logging import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SRA_CLOUDTRAIL_09(CloudTrailCheck):
|
|
11
|
+
"""Check if organization trails are publishing logs to CloudWatch Logs."""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
"""Initialize the check."""
|
|
15
|
+
super().__init__()
|
|
16
|
+
self.check_id = "SRA-CLOUDTRAIL-09"
|
|
17
|
+
self.check_name = "Organization trail is publishing logs to CloudWatch Logs"
|
|
18
|
+
self.account_type = "management"
|
|
19
|
+
self.severity = "MEDIUM"
|
|
20
|
+
self.description = (
|
|
21
|
+
"This check verifies that last attempt to send CloudTrail logs to CloudWatch Logs was successful. "
|
|
22
|
+
"Successful delivery of CloudTrails logs to CloudWatch ensures later availability for monitoring. "
|
|
23
|
+
"CloudTrail requires right permission to send log events to CloudWatch Logs."
|
|
24
|
+
)
|
|
25
|
+
self.check_logic = (
|
|
26
|
+
"Check if organization trails have LatestCloudWatchLogsDeliveryTime within the last 24 hours."
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
30
|
+
"""
|
|
31
|
+
Execute the check.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
List of findings
|
|
35
|
+
"""
|
|
36
|
+
findings = []
|
|
37
|
+
|
|
38
|
+
# Get organization trails
|
|
39
|
+
org_trails = self.get_organization_trails()
|
|
40
|
+
|
|
41
|
+
if not org_trails:
|
|
42
|
+
findings.append(
|
|
43
|
+
self.create_finding(
|
|
44
|
+
status="FAIL",
|
|
45
|
+
region="global",
|
|
46
|
+
resource_id=f"organization/{self.account_id}",
|
|
47
|
+
checked_value="LatestCloudWatchLogsDeliveryTime: within last 24 hours",
|
|
48
|
+
actual_value="No organization trails found",
|
|
49
|
+
remediation=(
|
|
50
|
+
"Create an organization trail with CloudWatch Logs delivery in the management account using the AWS CLI command: "
|
|
51
|
+
f"aws cloudtrail create-trail --name org-trail --is-organization-trail --s3-bucket-name cloudtrail-logs-{self.account_id} "
|
|
52
|
+
f"--cloud-watch-logs-log-group-arn arn:aws:logs:{self.regions[0] if self.regions else 'us-east-1'}:{self.account_id}:log-group:CloudTrail/Logs:* "
|
|
53
|
+
f"--cloud-watch-logs-role-arn arn:aws:iam::{self.account_id}:role/CloudTrail_CloudWatchLogs_Role "
|
|
54
|
+
f"--is-multi-region-trail --region {self.regions[0] if self.regions else 'us-east-1'}"
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
return findings
|
|
59
|
+
|
|
60
|
+
# Check each organization trail for CloudWatch Logs delivery
|
|
61
|
+
for trail in org_trails:
|
|
62
|
+
trail_name = trail.get('Name', 'Unknown')
|
|
63
|
+
trail_arn = trail.get('TrailARN', 'Unknown')
|
|
64
|
+
home_region = trail.get('HomeRegion', 'Unknown')
|
|
65
|
+
cloudwatch_logs_group_arn = trail.get('CloudWatchLogsLogGroupArn', '')
|
|
66
|
+
|
|
67
|
+
# Skip trails without CloudWatch Logs configuration
|
|
68
|
+
if not cloudwatch_logs_group_arn:
|
|
69
|
+
findings.append(
|
|
70
|
+
self.create_finding(
|
|
71
|
+
status="FAIL",
|
|
72
|
+
region="global",
|
|
73
|
+
resource_id=trail_arn,
|
|
74
|
+
checked_value="LatestCloudWatchLogsDeliveryTime: within last 24 hours",
|
|
75
|
+
actual_value=f"Organization trail '{trail_name}' is not configured to deliver logs to CloudWatch Logs",
|
|
76
|
+
remediation=(
|
|
77
|
+
f"Configure CloudTrail '{trail_name}' to use CloudWatch Logs using the AWS CLI command: "
|
|
78
|
+
f"aws cloudtrail update-trail --name {trail_name} "
|
|
79
|
+
f"--cloud-watch-logs-log-group-arn arn:aws:logs:{home_region}:{self.account_id}:log-group:CloudTrail/Logs:* "
|
|
80
|
+
f"--cloud-watch-logs-role-arn arn:aws:iam::{self.account_id}:role/CloudTrail_CloudWatchLogs_Role "
|
|
81
|
+
f"--region {home_region}"
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
# Get trail status to check CloudWatch Logs delivery
|
|
88
|
+
trail_status = self.get_trail_status(home_region, trail_arn)
|
|
89
|
+
latest_cloudwatch_logs_delivery_time_str = trail_status.get('LatestCloudWatchLogsDeliveryTime', None)
|
|
90
|
+
latest_cloudwatch_logs_delivery_error = trail_status.get('LatestCloudWatchLogsDeliveryError', None)
|
|
91
|
+
|
|
92
|
+
# Check if delivery time exists and is within the last 24 hours
|
|
93
|
+
if latest_cloudwatch_logs_delivery_time_str:
|
|
94
|
+
try:
|
|
95
|
+
# Convert string to datetime object
|
|
96
|
+
if isinstance(latest_cloudwatch_logs_delivery_time_str, str):
|
|
97
|
+
latest_delivery_time = datetime.fromisoformat(latest_cloudwatch_logs_delivery_time_str.replace('Z', '+00:00'))
|
|
98
|
+
else:
|
|
99
|
+
# Assume it's already a datetime object
|
|
100
|
+
latest_delivery_time = latest_cloudwatch_logs_delivery_time_str
|
|
101
|
+
|
|
102
|
+
# Get current time in UTC
|
|
103
|
+
now = datetime.now(timezone.utc)
|
|
104
|
+
|
|
105
|
+
# Use the delivery time as the resource ID
|
|
106
|
+
resource_id = f"cloudtrail arn delivery to CloudWatch logs within 24 hrs = true"
|
|
107
|
+
|
|
108
|
+
# Check if delivery was within the last 24 hours
|
|
109
|
+
if now - latest_delivery_time < timedelta(hours=24):
|
|
110
|
+
# Trail is delivering logs to CloudWatch Logs within the last 24 hours
|
|
111
|
+
findings.append(
|
|
112
|
+
self.create_finding(
|
|
113
|
+
status="PASS",
|
|
114
|
+
region="global",
|
|
115
|
+
resource_id=resource_id,
|
|
116
|
+
checked_value="LatestCloudWatchLogsDeliveryTime: within last 24 hours",
|
|
117
|
+
actual_value=f"Organization trail '{trail_name}' is publishing logs to CloudWatch Logs, latest delivery time: {latest_cloudwatch_logs_delivery_time_str}",
|
|
118
|
+
remediation="No remediation needed"
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
else:
|
|
122
|
+
# Trail has not delivered logs to CloudWatch Logs within the last 24 hours
|
|
123
|
+
findings.append(
|
|
124
|
+
self.create_finding(
|
|
125
|
+
status="FAIL",
|
|
126
|
+
region="global",
|
|
127
|
+
resource_id=resource_id,
|
|
128
|
+
checked_value="LatestCloudWatchLogsDeliveryTime: within last 24 hours",
|
|
129
|
+
actual_value=f"Organization trail '{trail_name}' has not published logs to CloudWatch Logs within the last 24 hours, latest delivery time: {latest_cloudwatch_logs_delivery_time_str}",
|
|
130
|
+
remediation=(
|
|
131
|
+
f"Check the CloudTrail configuration and CloudWatch Logs permissions. Ensure the trail is active using: "
|
|
132
|
+
f"aws cloudtrail start-logging --name {trail_name} --region {home_region}"
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
except (ValueError, TypeError) as e:
|
|
137
|
+
# Error parsing delivery time
|
|
138
|
+
findings.append(
|
|
139
|
+
self.create_finding(
|
|
140
|
+
status="FAIL",
|
|
141
|
+
region="global",
|
|
142
|
+
resource_id=trail_arn,
|
|
143
|
+
checked_value="LatestCloudWatchLogsDeliveryTime: within last 24 hours",
|
|
144
|
+
actual_value=f"Organization trail '{trail_name}' has an invalid CloudWatch Logs delivery time format: {latest_cloudwatch_logs_delivery_time_str}, error: {str(e)}",
|
|
145
|
+
remediation=(
|
|
146
|
+
f"Check the CloudTrail configuration and CloudWatch Logs permissions. Ensure the trail is active using: "
|
|
147
|
+
f"aws cloudtrail start-logging --name {trail_name} --region {home_region}"
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
else:
|
|
152
|
+
# No CloudWatch Logs delivery time found
|
|
153
|
+
findings.append(
|
|
154
|
+
self.create_finding(
|
|
155
|
+
status="FAIL",
|
|
156
|
+
region="global",
|
|
157
|
+
resource_id=trail_arn,
|
|
158
|
+
checked_value="LatestCloudWatchLogsDeliveryTime: within last 24 hours",
|
|
159
|
+
actual_value=f"Organization trail '{trail_name}' has no record of delivering logs to CloudWatch Logs",
|
|
160
|
+
remediation=(
|
|
161
|
+
f"Check the CloudTrail configuration and CloudWatch Logs permissions. Ensure the trail is active using: "
|
|
162
|
+
f"aws cloudtrail start-logging --name {trail_name} --region {home_region}"
|
|
163
|
+
)
|
|
164
|
+
)
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return findings
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SRA-CLOUDTRAIL-10: Organization CloudTrail Log File Validation Digest Delivery.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from datetime import datetime, timedelta, timezone
|
|
6
|
+
from sraverify.services.cloudtrail.base import CloudTrailCheck
|
|
7
|
+
from sraverify.core.logging import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SRA_CLOUDTRAIL_10(CloudTrailCheck):
|
|
11
|
+
"""Check if organization trails are delivering log file validation digest files."""
|
|
12
|
+
|
|
13
|
+
def __init__(self):
|
|
14
|
+
"""Initialize the check."""
|
|
15
|
+
super().__init__()
|
|
16
|
+
self.check_id = "SRA-CLOUDTRAIL-10"
|
|
17
|
+
self.check_name = "Organization trail is configured to deliver Log file validation digest files to destination bucket"
|
|
18
|
+
self.account_type = "management"
|
|
19
|
+
self.severity = "MEDIUM"
|
|
20
|
+
self.description = (
|
|
21
|
+
"This check verifies that log file validation digest files are being successfully delivered to a S3 bucket."
|
|
22
|
+
)
|
|
23
|
+
self.check_logic = (
|
|
24
|
+
"Check if organization trails have LatestDigestDeliveryTime within the last 24 hours."
|
|
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 organization trails
|
|
37
|
+
org_trails = self.get_organization_trails()
|
|
38
|
+
|
|
39
|
+
if not org_trails:
|
|
40
|
+
findings.append(
|
|
41
|
+
self.create_finding(
|
|
42
|
+
status="FAIL",
|
|
43
|
+
region="global",
|
|
44
|
+
resource_id=f"organization/{self.account_id}",
|
|
45
|
+
checked_value="LatestDigestDeliveryTime: within last 24 hours",
|
|
46
|
+
actual_value="No organization trails found",
|
|
47
|
+
remediation=(
|
|
48
|
+
"Create an organization trail with log file validation in the management account using the AWS CLI command: "
|
|
49
|
+
f"aws cloudtrail create-trail --name org-trail --is-organization-trail --s3-bucket-name cloudtrail-logs-{self.account_id} "
|
|
50
|
+
f"--enable-log-file-validation --is-multi-region-trail --region {self.regions[0] if self.regions else 'us-east-1'}"
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
return findings
|
|
55
|
+
|
|
56
|
+
# Check each organization trail for digest delivery
|
|
57
|
+
for trail in org_trails:
|
|
58
|
+
trail_name = trail.get('Name', 'Unknown')
|
|
59
|
+
trail_arn = trail.get('TrailARN', 'Unknown')
|
|
60
|
+
home_region = trail.get('HomeRegion', 'Unknown')
|
|
61
|
+
log_file_validation_enabled = trail.get('LogFileValidationEnabled', False)
|
|
62
|
+
s3_bucket_name = trail.get('S3BucketName', 'Unknown')
|
|
63
|
+
|
|
64
|
+
# Skip trails without log file validation enabled
|
|
65
|
+
if not log_file_validation_enabled:
|
|
66
|
+
findings.append(
|
|
67
|
+
self.create_finding(
|
|
68
|
+
status="FAIL",
|
|
69
|
+
region="global",
|
|
70
|
+
resource_id=trail_arn,
|
|
71
|
+
checked_value="LatestDigestDeliveryTime: within last 24 hours",
|
|
72
|
+
actual_value=f"Organization trail '{trail_name}' does not have log file validation enabled",
|
|
73
|
+
remediation=(
|
|
74
|
+
f"Enable log file validation for the organization trail '{trail_name}' using the AWS CLI command: "
|
|
75
|
+
f"aws cloudtrail update-trail --name {trail_name} --enable-log-file-validation --region {home_region}"
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
# Get trail status to check digest delivery
|
|
82
|
+
trail_status = self.get_trail_status(home_region, trail_arn)
|
|
83
|
+
latest_digest_delivery_time_str = trail_status.get('LatestDigestDeliveryTime', None)
|
|
84
|
+
latest_digest_delivery_error = trail_status.get('LatestDigestDeliveryError', None)
|
|
85
|
+
|
|
86
|
+
# Use the trail ARN with digest info as the resource ID
|
|
87
|
+
resource_id = f"cloudtrail arn of digest within 24 hrs = true"
|
|
88
|
+
|
|
89
|
+
# Check if digest delivery time exists and is within the last 24 hours
|
|
90
|
+
if latest_digest_delivery_time_str:
|
|
91
|
+
try:
|
|
92
|
+
# Convert string to datetime object
|
|
93
|
+
if isinstance(latest_digest_delivery_time_str, str):
|
|
94
|
+
latest_delivery_time = datetime.fromisoformat(latest_digest_delivery_time_str.replace('Z', '+00:00'))
|
|
95
|
+
else:
|
|
96
|
+
# Assume it's already a datetime object
|
|
97
|
+
latest_delivery_time = latest_digest_delivery_time_str
|
|
98
|
+
|
|
99
|
+
# Get current time in UTC
|
|
100
|
+
now = datetime.now(timezone.utc)
|
|
101
|
+
|
|
102
|
+
# Check if delivery was within the last 24 hours
|
|
103
|
+
if now - latest_delivery_time < timedelta(hours=24):
|
|
104
|
+
# Trail is delivering digest files within the last 24 hours
|
|
105
|
+
findings.append(
|
|
106
|
+
self.create_finding(
|
|
107
|
+
status="PASS",
|
|
108
|
+
region="global",
|
|
109
|
+
resource_id=resource_id,
|
|
110
|
+
checked_value="LatestDigestDeliveryTime: within last 24 hours",
|
|
111
|
+
actual_value=f"Organization trail '{trail_name}' is delivering log file validation digest files to S3 bucket '{s3_bucket_name}', latest delivery time: {latest_digest_delivery_time_str}",
|
|
112
|
+
remediation="No remediation needed"
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
else:
|
|
116
|
+
# Trail has not delivered digest files within the last 24 hours
|
|
117
|
+
findings.append(
|
|
118
|
+
self.create_finding(
|
|
119
|
+
status="FAIL",
|
|
120
|
+
region="global",
|
|
121
|
+
resource_id=resource_id,
|
|
122
|
+
checked_value="LatestDigestDeliveryTime: within last 24 hours",
|
|
123
|
+
actual_value=f"Organization trail '{trail_name}' has not delivered log file validation digest files to S3 bucket '{s3_bucket_name}' within the last 24 hours, latest delivery time: {latest_digest_delivery_time_str}",
|
|
124
|
+
remediation=(
|
|
125
|
+
f"Check the CloudTrail configuration and S3 bucket permissions. Ensure the trail is active using: "
|
|
126
|
+
f"aws cloudtrail start-logging --name {trail_name} --region {home_region}"
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
except (ValueError, TypeError) as e:
|
|
131
|
+
# Error parsing delivery time
|
|
132
|
+
findings.append(
|
|
133
|
+
self.create_finding(
|
|
134
|
+
status="FAIL",
|
|
135
|
+
region="global",
|
|
136
|
+
resource_id=trail_arn,
|
|
137
|
+
checked_value="LatestDigestDeliveryTime: within last 24 hours",
|
|
138
|
+
actual_value=f"Organization trail '{trail_name}' has an invalid digest delivery time format: {latest_digest_delivery_time_str}, error: {str(e)}",
|
|
139
|
+
remediation=(
|
|
140
|
+
f"Check the CloudTrail configuration and S3 bucket permissions. Ensure the trail is active using: "
|
|
141
|
+
f"aws cloudtrail start-logging --name {trail_name} --region {home_region}"
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
)
|
|
145
|
+
else:
|
|
146
|
+
# No digest delivery time found
|
|
147
|
+
findings.append(
|
|
148
|
+
self.create_finding(
|
|
149
|
+
status="FAIL",
|
|
150
|
+
region="global",
|
|
151
|
+
resource_id=trail_arn,
|
|
152
|
+
checked_value="LatestDigestDeliveryTime: within last 24 hours",
|
|
153
|
+
actual_value=f"Organization trail '{trail_name}' has no record of delivering log file validation digest files to S3 bucket '{s3_bucket_name}'",
|
|
154
|
+
remediation=(
|
|
155
|
+
f"Check the CloudTrail configuration and S3 bucket permissions. Ensure the trail is active and log file validation is enabled using: "
|
|
156
|
+
f"aws cloudtrail update-trail --name {trail_name} --enable-log-file-validation --region {home_region} && "
|
|
157
|
+
f"aws cloudtrail start-logging --name {trail_name} --region {home_region}"
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
return findings
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SRA-CLOUDTRAIL-11: Organization CloudTrail Logs Centralized in Log Archive Account.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from sraverify.services.cloudtrail.base import CloudTrailCheck
|
|
6
|
+
from sraverify.core.logging import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SRA_CLOUDTRAIL_11(CloudTrailCheck):
|
|
10
|
+
"""Check if organization trails logs are delivered to a centralized S3 bucket in the Log Archive account."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
"""Initialize the check."""
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.check_id = "SRA-CLOUDTRAIL-11"
|
|
16
|
+
self.check_name = "Organization trail Logs are delivered to a centralized S3 bucket in the Log Archive Account"
|
|
17
|
+
self.account_type = "management"
|
|
18
|
+
self.severity = "HIGH"
|
|
19
|
+
self.description = (
|
|
20
|
+
"This check verifies whether the corresponding S3 buckets that stores organization trail logs "
|
|
21
|
+
"in created in Log Archive account. This separates the management and usage of CloudTrail log "
|
|
22
|
+
"privileges. The Log Archive account is dedicated to ingesting and archiving all security-related "
|
|
23
|
+
"logs and backups."
|
|
24
|
+
)
|
|
25
|
+
self.check_logic = (
|
|
26
|
+
"Check if organization trails are configured to deliver logs to S3 buckets owned by "
|
|
27
|
+
"the Log Archive account by comparing the S3 bucket ARN with the provided Log Archive account IDs."
|
|
28
|
+
)
|
|
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
|
+
# Get organization trails
|
|
40
|
+
org_trails = self.get_organization_trails()
|
|
41
|
+
|
|
42
|
+
if not org_trails:
|
|
43
|
+
findings.append(
|
|
44
|
+
self.create_finding(
|
|
45
|
+
status="FAIL",
|
|
46
|
+
region="global",
|
|
47
|
+
resource_id=f"organization/{self.account_id}",
|
|
48
|
+
checked_value="S3 bucket in Log Archive account",
|
|
49
|
+
actual_value="No organization trails found",
|
|
50
|
+
remediation=(
|
|
51
|
+
"Create an organization trail with S3 bucket in the Log Archive account using the AWS CLI command: "
|
|
52
|
+
"aws cloudtrail create-trail --name org-trail --is-organization-trail "
|
|
53
|
+
"--s3-bucket-name aws-controltower-logs-{LOG_ARCHIVE_ACCOUNT_ID}-{REGION}"
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
return findings
|
|
58
|
+
|
|
59
|
+
# Check if log_archive_accounts is provided via _log_archive_accounts attribute
|
|
60
|
+
log_archive_accounts = []
|
|
61
|
+
if hasattr(self, '_log_archive_accounts') and self._log_archive_accounts:
|
|
62
|
+
log_archive_accounts = self._log_archive_accounts
|
|
63
|
+
|
|
64
|
+
if not log_archive_accounts:
|
|
65
|
+
findings.append(
|
|
66
|
+
self.create_finding(
|
|
67
|
+
status="ERROR",
|
|
68
|
+
region="global",
|
|
69
|
+
resource_id=f"organization/{self.account_id}",
|
|
70
|
+
checked_value="S3 bucket in Log Archive account",
|
|
71
|
+
actual_value="Log Archive Account ID not provided",
|
|
72
|
+
remediation="Provide the Log Archive account IDs using --log-archive-account flag"
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
return findings
|
|
76
|
+
|
|
77
|
+
# Check each organization trail for S3 bucket ownership
|
|
78
|
+
for trail in org_trails:
|
|
79
|
+
trail_name = trail.get('Name', 'Unknown')
|
|
80
|
+
trail_arn = trail.get('TrailARN', 'Unknown')
|
|
81
|
+
s3_bucket_name = trail.get('S3BucketName', '')
|
|
82
|
+
|
|
83
|
+
# Get the home region from the trail
|
|
84
|
+
home_region = trail.get('HomeRegion', 'Unknown')
|
|
85
|
+
|
|
86
|
+
# We need to determine the owner of the S3 bucket
|
|
87
|
+
# For this check, we'll use the bucket name to infer ownership
|
|
88
|
+
# Another implementation could be to make an S3 API call to get the bucket owner
|
|
89
|
+
# But for this example, we'll assume the bucket name contains the account ID or has a specific pattern
|
|
90
|
+
|
|
91
|
+
# Check if we can determine the bucket owner from the trail configuration
|
|
92
|
+
bucket_owner_account = None
|
|
93
|
+
|
|
94
|
+
# Try to get the bucket owner from the S3BucketOwnerName field if available
|
|
95
|
+
s3_bucket_owner = trail.get('S3BucketOwnerName', '')
|
|
96
|
+
if s3_bucket_owner:
|
|
97
|
+
# If we have the bucket owner name, we can check if it's in the log archive accounts
|
|
98
|
+
# This is a simplification - in reality, you'd need to map account IDs to account names
|
|
99
|
+
bucket_owner_account = s3_bucket_owner
|
|
100
|
+
|
|
101
|
+
# If we couldn't determine the bucket owner, check if the bucket name contains the account ID
|
|
102
|
+
if not bucket_owner_account:
|
|
103
|
+
for log_archive_account in log_archive_accounts:
|
|
104
|
+
if log_archive_account in s3_bucket_name:
|
|
105
|
+
bucket_owner_account = log_archive_account
|
|
106
|
+
break
|
|
107
|
+
|
|
108
|
+
# Create appropriate resource IDs based on whether the bucket is in the Log Archive account
|
|
109
|
+
if bucket_owner_account and bucket_owner_account in log_archive_accounts:
|
|
110
|
+
resource_id = f"cloudtrail logs being delivered to Log Archive account {log_archive_accounts[0]} and bucket name {s3_bucket_name}"
|
|
111
|
+
else:
|
|
112
|
+
resource_id = f"cloudtrail logs not being delivered to S3 bucket in the Log Archive account"
|
|
113
|
+
|
|
114
|
+
# If we still couldn't determine the bucket owner, we'll need to make an API call
|
|
115
|
+
# For this example, we'll just report that we couldn't determine the bucket owner
|
|
116
|
+
if not bucket_owner_account:
|
|
117
|
+
# Generate a recommended bucket name using the first log archive account
|
|
118
|
+
recommended_bucket_name = f"aws-controltower-logs-{log_archive_accounts[0]}-{home_region}"
|
|
119
|
+
|
|
120
|
+
findings.append(
|
|
121
|
+
self.create_finding(
|
|
122
|
+
status="FAIL",
|
|
123
|
+
region="global",
|
|
124
|
+
resource_id=resource_id,
|
|
125
|
+
checked_value=f"S3 bucket in Log Archive account ({', '.join(log_archive_accounts)})",
|
|
126
|
+
actual_value=(
|
|
127
|
+
f"Organization trail '{trail_name}' is using S3 bucket '{s3_bucket_name}' "
|
|
128
|
+
f"but the bucket owner could not be determined"
|
|
129
|
+
),
|
|
130
|
+
remediation=(
|
|
131
|
+
f"Update the organization trail '{trail_name}' to use an S3 bucket in the Log Archive account "
|
|
132
|
+
f"using the AWS CLI command: aws cloudtrail update-trail --name {trail_name} "
|
|
133
|
+
f"--s3-bucket-name {recommended_bucket_name}"
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
# Check if the bucket owner is in the log archive accounts
|
|
140
|
+
if bucket_owner_account in log_archive_accounts:
|
|
141
|
+
# Trail is using an S3 bucket in the Log Archive account
|
|
142
|
+
findings.append(
|
|
143
|
+
self.create_finding(
|
|
144
|
+
status="PASS",
|
|
145
|
+
region="global",
|
|
146
|
+
resource_id=resource_id,
|
|
147
|
+
checked_value=f"S3 bucket in Log Archive account ({', '.join(log_archive_accounts)})",
|
|
148
|
+
actual_value=(
|
|
149
|
+
f"Organization trail '{trail_name}' is using S3 bucket '{s3_bucket_name}' "
|
|
150
|
+
f"owned by Log Archive account {bucket_owner_account}"
|
|
151
|
+
),
|
|
152
|
+
remediation="No remediation needed"
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
else:
|
|
156
|
+
# Trail is not using an S3 bucket in the Log Archive account
|
|
157
|
+
# Generate a recommended bucket name using the first log archive account
|
|
158
|
+
recommended_bucket_name = f"aws-controltower-logs-{log_archive_accounts[0]}-{home_region}"
|
|
159
|
+
|
|
160
|
+
findings.append(
|
|
161
|
+
self.create_finding(
|
|
162
|
+
status="FAIL",
|
|
163
|
+
region="global",
|
|
164
|
+
resource_id=resource_id,
|
|
165
|
+
checked_value=f"S3 bucket in Log Archive account ({', '.join(log_archive_accounts)})",
|
|
166
|
+
actual_value=(
|
|
167
|
+
f"Organization trail '{trail_name}' is using S3 bucket '{s3_bucket_name}' "
|
|
168
|
+
f"owned by account {bucket_owner_account}, which is not a Log Archive account"
|
|
169
|
+
),
|
|
170
|
+
remediation=(
|
|
171
|
+
f"Update the organization trail '{trail_name}' to use an S3 bucket in the Log Archive account "
|
|
172
|
+
f"using the AWS CLI command: aws cloudtrail update-trail --name {trail_name} "
|
|
173
|
+
f"--s3-bucket-name {recommended_bucket_name}"
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
return findings
|