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.
Files changed (261) hide show
  1. sraverify/__init__.py +36 -0
  2. sraverify/checks/__init__.py +56 -0
  3. sraverify/checks/accessanalyzer/SRA_IAA_1.py +188 -0
  4. sraverify/checks/accessanalyzer/SRA_IAA_2.py +162 -0
  5. sraverify/checks/accessanalyzer/SRA_IAA_3.py +260 -0
  6. sraverify/checks/accessanalyzer/SRA_IAA_4.py +207 -0
  7. sraverify/checks/accessanalyzer/__init__.py +3 -0
  8. sraverify/checks/cloudtrail/SRA-CT-1.py +220 -0
  9. sraverify/checks/cloudtrail/SRA-CT-10.py +229 -0
  10. sraverify/checks/cloudtrail/SRA-CT-11.py +242 -0
  11. sraverify/checks/cloudtrail/SRA-CT-12.py +163 -0
  12. sraverify/checks/cloudtrail/SRA-CT-13.py +279 -0
  13. sraverify/checks/cloudtrail/SRA-CT-2.py +218 -0
  14. sraverify/checks/cloudtrail/SRA-CT-3.py +196 -0
  15. sraverify/checks/cloudtrail/SRA-CT-4.py +161 -0
  16. sraverify/checks/cloudtrail/SRA-CT-5.py +200 -0
  17. sraverify/checks/cloudtrail/SRA-CT-6.py +161 -0
  18. sraverify/checks/cloudtrail/SRA-CT-7.py +194 -0
  19. sraverify/checks/cloudtrail/SRA-CT-8.py +226 -0
  20. sraverify/checks/cloudtrail/SRA-CT-9.py +226 -0
  21. sraverify/checks/cloudtrail/__init__.py +3 -0
  22. sraverify/checks/config/SRA-CONFIG-1.py +197 -0
  23. sraverify/checks/config/__init__.py +3 -0
  24. sraverify/core/__init__.py +3 -0
  25. sraverify/core/check.py +227 -0
  26. sraverify/core/logging.py +37 -0
  27. sraverify/core/session.py +47 -0
  28. sraverify/lib/__init__.py +4 -0
  29. sraverify/lib/audit_info.py +37 -0
  30. sraverify/lib/banner.py +42 -0
  31. sraverify/lib/check_loader.py +80 -0
  32. sraverify/lib/org_mgmt_checker.py +86 -0
  33. sraverify/lib/outputs.py +46 -0
  34. sraverify/lib/progress.py +75 -0
  35. sraverify/lib/regions.py +27 -0
  36. sraverify/lib/session.py +23 -0
  37. sraverify/main.py +350 -0
  38. sraverify/services/__init__.py +3 -0
  39. sraverify/services/accessanalyzer/__init__.py +15 -0
  40. sraverify/services/accessanalyzer/base.py +123 -0
  41. sraverify/services/accessanalyzer/checks/__init__.py +3 -0
  42. sraverify/services/accessanalyzer/checks/sra_accessanalyzer_01.py +82 -0
  43. sraverify/services/accessanalyzer/checks/sra_accessanalyzer_02.py +82 -0
  44. sraverify/services/accessanalyzer/checks/sra_accessanalyzer_03.py +103 -0
  45. sraverify/services/accessanalyzer/checks/sra_accessanalyzer_04.py +139 -0
  46. sraverify/services/accessanalyzer/client.py +123 -0
  47. sraverify/services/account/__init__.py +9 -0
  48. sraverify/services/account/base.py +56 -0
  49. sraverify/services/account/checks/__init__.py +1 -0
  50. sraverify/services/account/checks/sra_account_01.py +65 -0
  51. sraverify/services/account/checks/sra_account_02.py +63 -0
  52. sraverify/services/account/checks/sra_account_03.py +63 -0
  53. sraverify/services/account/client.py +51 -0
  54. sraverify/services/auditmanager/__init__.py +10 -0
  55. sraverify/services/auditmanager/base.py +72 -0
  56. sraverify/services/auditmanager/checks/__init__.py +1 -0
  57. sraverify/services/auditmanager/checks/sra_auditmanager_01.py +58 -0
  58. sraverify/services/auditmanager/checks/sra_auditmanager_02.py +80 -0
  59. sraverify/services/auditmanager/client.py +58 -0
  60. sraverify/services/cloudtrail/__init__.py +33 -0
  61. sraverify/services/cloudtrail/base.py +167 -0
  62. sraverify/services/cloudtrail/checks/__init__.py +1 -0
  63. sraverify/services/cloudtrail/checks/sra_cloudtrail_01.py +83 -0
  64. sraverify/services/cloudtrail/checks/sra_cloudtrail_02.py +99 -0
  65. sraverify/services/cloudtrail/checks/sra_cloudtrail_03.py +94 -0
  66. sraverify/services/cloudtrail/checks/sra_cloudtrail_04.py +92 -0
  67. sraverify/services/cloudtrail/checks/sra_cloudtrail_05.py +106 -0
  68. sraverify/services/cloudtrail/checks/sra_cloudtrail_06.py +93 -0
  69. sraverify/services/cloudtrail/checks/sra_cloudtrail_07.py +96 -0
  70. sraverify/services/cloudtrail/checks/sra_cloudtrail_08.py +145 -0
  71. sraverify/services/cloudtrail/checks/sra_cloudtrail_09.py +167 -0
  72. sraverify/services/cloudtrail/checks/sra_cloudtrail_10.py +162 -0
  73. sraverify/services/cloudtrail/checks/sra_cloudtrail_11.py +178 -0
  74. sraverify/services/cloudtrail/checks/sra_cloudtrail_12.py +77 -0
  75. sraverify/services/cloudtrail/checks/sra_cloudtrail_13.py +120 -0
  76. sraverify/services/cloudtrail/client.py +118 -0
  77. sraverify/services/config/__init__.py +25 -0
  78. sraverify/services/config/base.py +249 -0
  79. sraverify/services/config/checks/__init__.py +1 -0
  80. sraverify/services/config/checks/sra_config_01.py +123 -0
  81. sraverify/services/config/checks/sra_config_02.py +156 -0
  82. sraverify/services/config/checks/sra_config_03.py +149 -0
  83. sraverify/services/config/checks/sra_config_04.py +104 -0
  84. sraverify/services/config/checks/sra_config_05.py +104 -0
  85. sraverify/services/config/checks/sra_config_06.py +194 -0
  86. sraverify/services/config/checks/sra_config_07.py +162 -0
  87. sraverify/services/config/checks/sra_config_08.py +185 -0
  88. sraverify/services/config/checks/sra_config_09.py +177 -0
  89. sraverify/services/config/client.py +264 -0
  90. sraverify/services/ec2/__init__.py +8 -0
  91. sraverify/services/ec2/base.py +75 -0
  92. sraverify/services/ec2/checks/__init__.py +1 -0
  93. sraverify/services/ec2/checks/sra_ec2_01.py +83 -0
  94. sraverify/services/ec2/client.py +63 -0
  95. sraverify/services/firewallmanager/__init__.py +23 -0
  96. sraverify/services/firewallmanager/base.py +48 -0
  97. sraverify/services/firewallmanager/checks/__init__.py +1 -0
  98. sraverify/services/firewallmanager/checks/sra_firewallmanager_01.py +75 -0
  99. sraverify/services/firewallmanager/checks/sra_firewallmanager_02.py +57 -0
  100. sraverify/services/firewallmanager/checks/sra_firewallmanager_03.py +51 -0
  101. sraverify/services/firewallmanager/checks/sra_firewallmanager_04.py +51 -0
  102. sraverify/services/firewallmanager/checks/sra_firewallmanager_05.py +51 -0
  103. sraverify/services/firewallmanager/checks/sra_firewallmanager_06.py +51 -0
  104. sraverify/services/firewallmanager/checks/sra_firewallmanager_07.py +51 -0
  105. sraverify/services/firewallmanager/checks/sra_firewallmanager_08.py +61 -0
  106. sraverify/services/firewallmanager/checks/sra_firewallmanager_09.py +61 -0
  107. sraverify/services/firewallmanager/checks/sra_firewallmanager_10.py +71 -0
  108. sraverify/services/firewallmanager/client.py +40 -0
  109. sraverify/services/guardduty/__init__.py +58 -0
  110. sraverify/services/guardduty/base.py +207 -0
  111. sraverify/services/guardduty/checks/__init__.py +3 -0
  112. sraverify/services/guardduty/checks/sra_guardduty_01.py +51 -0
  113. sraverify/services/guardduty/checks/sra_guardduty_02.py +80 -0
  114. sraverify/services/guardduty/checks/sra_guardduty_03.py +77 -0
  115. sraverify/services/guardduty/checks/sra_guardduty_04.py +84 -0
  116. sraverify/services/guardduty/checks/sra_guardduty_05.py +84 -0
  117. sraverify/services/guardduty/checks/sra_guardduty_06.py +84 -0
  118. sraverify/services/guardduty/checks/sra_guardduty_07.py +85 -0
  119. sraverify/services/guardduty/checks/sra_guardduty_08.py +83 -0
  120. sraverify/services/guardduty/checks/sra_guardduty_09.py +84 -0
  121. sraverify/services/guardduty/checks/sra_guardduty_10.py +83 -0
  122. sraverify/services/guardduty/checks/sra_guardduty_11.py +93 -0
  123. sraverify/services/guardduty/checks/sra_guardduty_12.py +83 -0
  124. sraverify/services/guardduty/checks/sra_guardduty_13.py +90 -0
  125. sraverify/services/guardduty/checks/sra_guardduty_14.py +136 -0
  126. sraverify/services/guardduty/checks/sra_guardduty_15.py +94 -0
  127. sraverify/services/guardduty/checks/sra_guardduty_16.py +94 -0
  128. sraverify/services/guardduty/checks/sra_guardduty_17.py +91 -0
  129. sraverify/services/guardduty/checks/sra_guardduty_18.py +91 -0
  130. sraverify/services/guardduty/checks/sra_guardduty_19.py +91 -0
  131. sraverify/services/guardduty/checks/sra_guardduty_20.py +111 -0
  132. sraverify/services/guardduty/checks/sra_guardduty_21.py +112 -0
  133. sraverify/services/guardduty/checks/sra_guardduty_22.py +111 -0
  134. sraverify/services/guardduty/checks/sra_guardduty_23.py +154 -0
  135. sraverify/services/guardduty/checks/sra_guardduty_24.py +111 -0
  136. sraverify/services/guardduty/checks/sra_guardduty_25.py +111 -0
  137. sraverify/services/guardduty/client.py +107 -0
  138. sraverify/services/inspector/__init__.py +29 -0
  139. sraverify/services/inspector/base.py +233 -0
  140. sraverify/services/inspector/checks/__init__.py +3 -0
  141. sraverify/services/inspector/checks/sra_inspector_01.py +69 -0
  142. sraverify/services/inspector/checks/sra_inspector_02.py +68 -0
  143. sraverify/services/inspector/checks/sra_inspector_03.py +68 -0
  144. sraverify/services/inspector/checks/sra_inspector_04.py +70 -0
  145. sraverify/services/inspector/checks/sra_inspector_05.py +69 -0
  146. sraverify/services/inspector/checks/sra_inspector_06.py +115 -0
  147. sraverify/services/inspector/checks/sra_inspector_07.py +109 -0
  148. sraverify/services/inspector/checks/sra_inspector_08.py +69 -0
  149. sraverify/services/inspector/checks/sra_inspector_09.py +69 -0
  150. sraverify/services/inspector/checks/sra_inspector_10.py +69 -0
  151. sraverify/services/inspector/checks/sra_inspector_11.py +69 -0
  152. sraverify/services/inspector/client.py +99 -0
  153. sraverify/services/macie/__init__.py +27 -0
  154. sraverify/services/macie/base.py +271 -0
  155. sraverify/services/macie/checks/__init__.py +1 -0
  156. sraverify/services/macie/checks/sra_macie_01.py +100 -0
  157. sraverify/services/macie/checks/sra_macie_02.py +102 -0
  158. sraverify/services/macie/checks/sra_macie_03.py +152 -0
  159. sraverify/services/macie/checks/sra_macie_04.py +120 -0
  160. sraverify/services/macie/checks/sra_macie_05.py +85 -0
  161. sraverify/services/macie/checks/sra_macie_06.py +124 -0
  162. sraverify/services/macie/checks/sra_macie_07.py +138 -0
  163. sraverify/services/macie/checks/sra_macie_08.py +82 -0
  164. sraverify/services/macie/checks/sra_macie_09.py +103 -0
  165. sraverify/services/macie/checks/sra_macie_10.py +81 -0
  166. sraverify/services/macie/client.py +220 -0
  167. sraverify/services/s3/__init__.py +16 -0
  168. sraverify/services/s3/base.py +69 -0
  169. sraverify/services/s3/checks/__init__.py +1 -0
  170. sraverify/services/s3/checks/sra_s3_01.py +89 -0
  171. sraverify/services/s3/checks/sra_s3_02.py +89 -0
  172. sraverify/services/s3/checks/sra_s3_03.py +88 -0
  173. sraverify/services/s3/checks/sra_s3_04.py +88 -0
  174. sraverify/services/s3/client.py +52 -0
  175. sraverify/services/securityhub/__init__.py +27 -0
  176. sraverify/services/securityhub/base.py +349 -0
  177. sraverify/services/securityhub/checks/__init__.py +1 -0
  178. sraverify/services/securityhub/checks/sra_securityhub_01.py +115 -0
  179. sraverify/services/securityhub/checks/sra_securityhub_02.py +114 -0
  180. sraverify/services/securityhub/checks/sra_securityhub_03.py +136 -0
  181. sraverify/services/securityhub/checks/sra_securityhub_04.py +75 -0
  182. sraverify/services/securityhub/checks/sra_securityhub_05.py +102 -0
  183. sraverify/services/securityhub/checks/sra_securityhub_06.py +113 -0
  184. sraverify/services/securityhub/checks/sra_securityhub_07.py +121 -0
  185. sraverify/services/securityhub/checks/sra_securityhub_08.py +113 -0
  186. sraverify/services/securityhub/checks/sra_securityhub_09.py +100 -0
  187. sraverify/services/securityhub/checks/sra_securityhub_10.py +94 -0
  188. sraverify/services/securityhub/checks/sra_securityhub_11.py +73 -0
  189. sraverify/services/securityhub/client.py +249 -0
  190. sraverify/services/securityincidentresponse/__init__.py +13 -0
  191. sraverify/services/securityincidentresponse/base.py +95 -0
  192. sraverify/services/securityincidentresponse/checks/__init__.py +1 -0
  193. sraverify/services/securityincidentresponse/checks/sra_securityincidentresponse_01.py +77 -0
  194. sraverify/services/securityincidentresponse/checks/sra_securityincidentresponse_02.py +72 -0
  195. sraverify/services/securityincidentresponse/checks/sra_securityincidentresponse_03.py +86 -0
  196. sraverify/services/securityincidentresponse/checks/sra_securityincidentresponse_04.py +117 -0
  197. sraverify/services/securityincidentresponse/checks/sra_securityincidentresponse_05.py +55 -0
  198. sraverify/services/securityincidentresponse/client.py +71 -0
  199. sraverify/services/securitylake/__init__.py +39 -0
  200. sraverify/services/securitylake/base.py +461 -0
  201. sraverify/services/securitylake/checks/__init__.py +1 -0
  202. sraverify/services/securitylake/checks/sra_securitylake_01.py +98 -0
  203. sraverify/services/securitylake/checks/sra_securitylake_02.py +133 -0
  204. sraverify/services/securitylake/checks/sra_securitylake_03.py +116 -0
  205. sraverify/services/securitylake/checks/sra_securitylake_04.py +72 -0
  206. sraverify/services/securitylake/checks/sra_securitylake_05.py +116 -0
  207. sraverify/services/securitylake/checks/sra_securitylake_06.py +104 -0
  208. sraverify/services/securitylake/checks/sra_securitylake_07.py +108 -0
  209. sraverify/services/securitylake/checks/sra_securitylake_08.py +107 -0
  210. sraverify/services/securitylake/checks/sra_securitylake_09.py +107 -0
  211. sraverify/services/securitylake/checks/sra_securitylake_10.py +106 -0
  212. sraverify/services/securitylake/checks/sra_securitylake_11.py +109 -0
  213. sraverify/services/securitylake/checks/sra_securitylake_12.py +108 -0
  214. sraverify/services/securitylake/checks/sra_securitylake_13.py +108 -0
  215. sraverify/services/securitylake/checks/sra_securitylake_14.py +72 -0
  216. sraverify/services/securitylake/checks/sra_securitylake_15.py +120 -0
  217. sraverify/services/securitylake/checks/sra_securitylake_16.py +104 -0
  218. sraverify/services/securitylake/checks/sra_securitylake_17.py +103 -0
  219. sraverify/services/securitylake/client.py +247 -0
  220. sraverify/services/shield/__init__.py +33 -0
  221. sraverify/services/shield/base.py +199 -0
  222. sraverify/services/shield/checks/__init__.py +1 -0
  223. sraverify/services/shield/checks/sra_shield_01.py +68 -0
  224. sraverify/services/shield/checks/sra_shield_02.py +77 -0
  225. sraverify/services/shield/checks/sra_shield_03.py +84 -0
  226. sraverify/services/shield/checks/sra_shield_04.py +84 -0
  227. sraverify/services/shield/checks/sra_shield_05.py +84 -0
  228. sraverify/services/shield/checks/sra_shield_06.py +84 -0
  229. sraverify/services/shield/checks/sra_shield_07.py +84 -0
  230. sraverify/services/shield/checks/sra_shield_08.py +69 -0
  231. sraverify/services/shield/checks/sra_shield_09.py +86 -0
  232. sraverify/services/shield/checks/sra_shield_10.py +100 -0
  233. sraverify/services/shield/checks/sra_shield_11.py +71 -0
  234. sraverify/services/shield/checks/sra_shield_12.py +130 -0
  235. sraverify/services/shield/checks/sra_shield_13.py +112 -0
  236. sraverify/services/shield/checks/sra_shield_14.py +111 -0
  237. sraverify/services/shield/client.py +214 -0
  238. sraverify/services/waf/__init__.py +21 -0
  239. sraverify/services/waf/base.py +100 -0
  240. sraverify/services/waf/checks/__init__.py +1 -0
  241. sraverify/services/waf/checks/sra_waf_01.py +63 -0
  242. sraverify/services/waf/checks/sra_waf_02.py +82 -0
  243. sraverify/services/waf/checks/sra_waf_03.py +123 -0
  244. sraverify/services/waf/checks/sra_waf_04.py +94 -0
  245. sraverify/services/waf/checks/sra_waf_05.py +94 -0
  246. sraverify/services/waf/checks/sra_waf_06.py +91 -0
  247. sraverify/services/waf/checks/sra_waf_07.py +94 -0
  248. sraverify/services/waf/checks/sra_waf_08.py +66 -0
  249. sraverify/services/waf/checks/sra_waf_09.py +95 -0
  250. sraverify/services/waf/client.py +109 -0
  251. sraverify/utils/__init__.py +3 -0
  252. sraverify/utils/banner.py +65 -0
  253. sraverify/utils/outputs.py +57 -0
  254. sraverify/utils/progress.py +97 -0
  255. sraverify-0.1.0.dist-info/LICENSE +175 -0
  256. sraverify-0.1.0.dist-info/METADATA +516 -0
  257. sraverify-0.1.0.dist-info/NOTICE +1 -0
  258. sraverify-0.1.0.dist-info/RECORD +261 -0
  259. sraverify-0.1.0.dist-info/WHEEL +5 -0
  260. sraverify-0.1.0.dist-info/entry_points.txt +2 -0
  261. 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