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,226 @@
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 SRACT9(SecurityCheck):
8
+ """SRA-CT-9: Organization Trail CloudWatch Logs Delivery 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-9"
14
+ self.check_name = "Organization trail is publishing logs to CloudWatch Logs"
15
+ self.description = ('This check verifies that last attempt to send CloudTrail logs to CloudWatch Logs was successful. '
16
+ 'Successful delivery of CloudTrails logs to CloudWatch ensures later availability for monitoring. '
17
+ 'CloudTrail requires right permission to send log events to CloudWatch Logs.')
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 CloudWatch Logs configuration and successful delivery by checking: '
25
+ 'a) CloudWatch Logs group is configured, b) IAM role is configured, '
26
+ 'c) No delivery errors exist, d) Latest 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_cloudwatch_delivery_status(self, cloudtrail_client, trail_name: str) -> tuple:
35
+ """Check CloudWatch Logs delivery status and return status details"""
36
+ try:
37
+ status = cloudtrail_client.get_trail_status(Name=trail_name)
38
+ latest_delivery_time = status.get('LatestCloudWatchLogsDeliveryTime')
39
+ latest_delivery_error = status.get('LatestCloudWatchLogsDeliveryError', '')
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 CloudWatch Logs configuration",
112
+ "Service": self.service,
113
+ "CheckLogic": self.check_logic,
114
+ "CheckType": self.check_type
115
+ })
116
+ return
117
+
118
+ # Check each organization trail for CloudWatch Logs delivery status
119
+ valid_trail = None
120
+ for trail in org_trails:
121
+ trail_name = trail['Name']
122
+ cloudwatch_logs_group = trail.get('CloudWatchLogsLogGroupArn')
123
+ cloudwatch_logs_role = trail.get('CloudWatchLogsRoleArn')
124
+
125
+ # Skip if CloudWatch Logs is not configured
126
+ if not (cloudwatch_logs_group and cloudwatch_logs_role):
127
+ continue
128
+
129
+ is_logging, is_recent, delivery_error = self.check_cloudwatch_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 CloudWatch Logs delivery: {trail_name}")
134
+ break
135
+
136
+ # Create finding based on CloudWatch Logs 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": "CloudWatch Logs Delivery Status",
149
+ "ActualValue": f"Organization trail {valid_trail['Name']} is successfully delivering logs to CloudWatch Logs group",
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 CloudWatch Logs delivery"
157
+ remediation = "Verify CloudWatch Logs configuration and permissions"
158
+ if org_trails:
159
+ trail = org_trails[0]
160
+ if not (trail.get('CloudWatchLogsLogGroupArn') and trail.get('CloudWatchLogsRoleArn')):
161
+ actual_value = f"Trail {trail['Name']} has incomplete CloudWatch Logs configuration"
162
+ remediation = "Configure CloudWatch Logs group and IAM role for the organization trail"
163
+ elif delivery_error:
164
+ actual_value = f"Trail {trail['Name']} has delivery error: {delivery_error}"
165
+ remediation = "Resolve CloudWatch Logs delivery errors (check IAM role permissions)"
166
+ elif not is_recent:
167
+ actual_value = f"Trail {trail['Name']} has no recent CloudWatch Logs delivery"
168
+ remediation = "Verify trail logging is enabled and check CloudWatch Logs permissions"
169
+
170
+ self.findings.append({
171
+ "CheckId": self.check_id,
172
+ "Status": "FAIL",
173
+ "Region": region,
174
+ "Severity": self.severity,
175
+ "Title": f"{self.check_id} {self.check_name}",
176
+ "Description": self.description,
177
+ "ResourceId": (valid_trail or org_trails[0])['TrailARN'],
178
+ "ResourceType": "AWS::CloudTrail::Trail",
179
+ "AccountId": account_id,
180
+ "CheckedValue": "CloudWatch Logs Delivery Status",
181
+ "ActualValue": actual_value,
182
+ "Remediation": remediation,
183
+ "Service": self.service,
184
+ "CheckLogic": self.check_logic,
185
+ "CheckType": self.check_type
186
+ })
187
+
188
+ except ClientError as e:
189
+ self.logger.error(f"Error accessing CloudTrail: {str(e)}")
190
+ self.findings.append({
191
+ "CheckId": self.check_id,
192
+ "Status": "ERROR",
193
+ "Region": region,
194
+ "Severity": self.severity,
195
+ "Title": f"{self.check_id} {self.check_name}",
196
+ "Description": self.description,
197
+ "ResourceId": "cloudtrail",
198
+ "ResourceType": "AWS::CloudTrail::Trail",
199
+ "AccountId": account_id,
200
+ "CheckedValue": "CloudTrail API Access",
201
+ "ActualValue": f"Error accessing CloudTrail: {str(e)}",
202
+ "Remediation": "Verify CloudTrail permissions",
203
+ "Service": self.service,
204
+ "CheckLogic": self.check_logic,
205
+ "CheckType": self.check_type
206
+ })
207
+
208
+ except Exception as e:
209
+ self.logger.error(f"Unexpected error in check: {str(e)}")
210
+ self.findings.append({
211
+ "CheckId": self.check_id,
212
+ "Status": "ERROR",
213
+ "Region": region if 'region' in locals() else session.region_name,
214
+ "Severity": self.severity,
215
+ "Title": f"{self.check_id} {self.check_name}",
216
+ "Description": self.description,
217
+ "ResourceId": "check-execution",
218
+ "ResourceType": "AWS::CloudTrail::Trail",
219
+ "AccountId": account_id if 'account_id' in locals() else "unknown",
220
+ "CheckedValue": "Check Execution",
221
+ "ActualValue": f"Unexpected error: {str(e)}",
222
+ "Remediation": "Contact support team",
223
+ "Service": self.service,
224
+ "CheckLogic": self.check_logic,
225
+ "CheckType": self.check_type
226
+ })
@@ -0,0 +1,3 @@
1
+ """
2
+ Package containing Cloud Trail security checks.
3
+ """
@@ -0,0 +1,197 @@
1
+ from typing import Dict, List, Any, Optional
2
+ from botocore.exceptions import ClientError
3
+ import logging
4
+ from sraverify.lib.check_loader import SecurityCheck
5
+
6
+ class SRACONFIG1(SecurityCheck):
7
+ """SRA-CONFIG-1: AWS Config recorder configuration check"""
8
+
9
+ def __init__(self, check_type="account"):
10
+ """Initialize the check with account type"""
11
+ super().__init__(check_type=check_type)
12
+ self.check_id = "SRA-CONFIG-1"
13
+ self.check_name = "AWS Config recorder is configured in this region"
14
+ self.description = ('This check verifies that a configuration recorders exists in the AWS Region. '
15
+ 'AWS Config uses the configuration recorder to detect changes in your resource '
16
+ 'configurations and capture these changes as configuration items. You must create '
17
+ 'a configuration recorder in every AWS Region for AWS Config can track your '
18
+ 'resource configurations in the region.')
19
+ self.service = "Config"
20
+ self.severity = "HIGH"
21
+ self.check_type = check_type
22
+ self.check_logic = ('1. List Config recorders in current region | '
23
+ '2. Verify at least one recorder exists | '
24
+ '3. Check recorder configuration status')
25
+ self.logger = logging.getLogger(self.__class__.__name__)
26
+ self.findings = []
27
+ self._regions = None
28
+
29
+ def initialize(self, regions: Optional[List[str]] = None):
30
+ """Initialize check with optional regions"""
31
+ self._regions = regions
32
+
33
+ def get_findings(self) -> List[Dict[str, Any]]:
34
+ """Return the findings"""
35
+ return self.findings
36
+
37
+ def _create_finding(self, status: str, region: str, account_id: str,
38
+ resource_id: str, actual_value: str,
39
+ remediation: str) -> Dict[str, Any]:
40
+ """Create a standardized finding"""
41
+ return {
42
+ "CheckId": self.check_id,
43
+ "Status": status,
44
+ "Region": region,
45
+ "Severity": self.severity,
46
+ "Title": f"{self.check_id} {self.check_name}",
47
+ "Description": self.description,
48
+ "ResourceId": resource_id,
49
+ "ResourceType": "AWS::Config::ConfigurationRecorder",
50
+ "AccountId": account_id,
51
+ "CheckedValue": "Configuration Recorder Status",
52
+ "ActualValue": actual_value,
53
+ "Remediation": remediation,
54
+ "Service": self.service,
55
+ "CheckLogic": self.check_logic,
56
+ "CheckType": self.check_type
57
+ }
58
+
59
+ def check_recorder_status(self, config_client, recorder_name: str) -> tuple:
60
+ """Check Config recorder status and return details"""
61
+ try:
62
+ status = config_client.describe_configuration_recorder_status(
63
+ ConfigurationRecorderNames=[recorder_name]
64
+ )
65
+ if status['ConfigurationRecordersStatus']:
66
+ recorder_status = status['ConfigurationRecordersStatus'][0]
67
+ return (
68
+ recorder_status.get('recording', False),
69
+ recorder_status.get('lastStatus', 'ERROR'),
70
+ recorder_status.get('lastErrorCode', ''),
71
+ recorder_status.get('lastErrorMessage', '')
72
+ )
73
+ return False, 'ERROR', 'NO_STATUS', 'No recorder status found'
74
+
75
+ except ClientError as e:
76
+ self.logger.error(f"Error getting recorder status for {recorder_name}: {str(e)}")
77
+ return False, 'ERROR', str(e), 'Error getting recorder status'
78
+
79
+ def _check_region(self, session, region: str) -> Optional[Dict[str, Any]]:
80
+ """Check Config configuration in a specific region"""
81
+ try:
82
+ account_id = session.client('sts').get_caller_identity()['Account']
83
+ self.logger.debug(f"Checking region: {region}")
84
+
85
+ # Initialize Config client for the specific region
86
+ config_client = session.client('config', region_name=region)
87
+
88
+ try:
89
+ # List configuration recorders
90
+ recorders = config_client.describe_configuration_recorders()
91
+ if not recorders['ConfigurationRecorders']:
92
+ return self._create_finding(
93
+ status="FAIL",
94
+ region=region,
95
+ account_id=account_id,
96
+ resource_id="config-recorder",
97
+ actual_value="No configuration recorder found in region",
98
+ remediation="Create a configuration recorder in this region"
99
+ )
100
+
101
+ # Check each recorder's status
102
+ valid_recorder = None
103
+ for recorder in recorders['ConfigurationRecorders']:
104
+ recorder_name = recorder['name']
105
+ is_recording, last_status, error_code, error_message = self.check_recorder_status(
106
+ config_client,
107
+ recorder_name
108
+ )
109
+
110
+ if is_recording and last_status == 'SUCCESS':
111
+ valid_recorder = recorder
112
+ self.logger.debug(f"Found active recorder: {recorder_name}")
113
+ break
114
+
115
+ if valid_recorder:
116
+ return self._create_finding(
117
+ status="PASS",
118
+ region=region,
119
+ account_id=account_id,
120
+ resource_id=valid_recorder['name'],
121
+ actual_value=f"Configuration recorder {valid_recorder['name']} is active and recording",
122
+ remediation="None required"
123
+ )
124
+ else:
125
+ recorder = recorders['ConfigurationRecorders'][0]
126
+ actual_value = f"Configuration recorder {recorder['name']} exists but "
127
+ if not is_recording:
128
+ actual_value += "is not recording"
129
+ else:
130
+ actual_value += f"has status: {last_status}"
131
+ if error_code:
132
+ actual_value += f" (Error: {error_code} - {error_message})"
133
+
134
+ return self._create_finding(
135
+ status="FAIL",
136
+ region=region,
137
+ account_id=account_id,
138
+ resource_id=recorder['name'],
139
+ actual_value=actual_value,
140
+ remediation="Start the configuration recorder or fix configuration errors"
141
+ )
142
+
143
+ except ClientError as e:
144
+ return self._create_finding(
145
+ status="ERROR",
146
+ region=region,
147
+ account_id=account_id,
148
+ resource_id="config",
149
+ actual_value=f"Error accessing Config: {str(e)}",
150
+ remediation="Verify Config permissions"
151
+ )
152
+
153
+ except Exception as e:
154
+ return self._create_finding(
155
+ status="ERROR",
156
+ region=region,
157
+ account_id="Unknown",
158
+ resource_id="check-execution",
159
+ actual_value=f"Error: {str(e)}",
160
+ remediation="Check logs for more details"
161
+ )
162
+
163
+ def run(self, session) -> None:
164
+ """Run the security check"""
165
+ try:
166
+ # Get regions to check
167
+ regions_to_check = self._regions if self._regions else [session.region_name]
168
+
169
+ # Check each region
170
+ for region in regions_to_check:
171
+ try:
172
+ finding = self._check_region(session, region)
173
+ if finding:
174
+ self.findings.append(finding)
175
+ except Exception as e:
176
+ self.findings.append(
177
+ self._create_finding(
178
+ status="ERROR",
179
+ region=region,
180
+ account_id="Unknown",
181
+ resource_id="Unknown",
182
+ actual_value=f"Region check failed: {str(e)}",
183
+ remediation="Check regional access and permissions"
184
+ )
185
+ )
186
+
187
+ except Exception as e:
188
+ self.findings.append(
189
+ self._create_finding(
190
+ status="ERROR",
191
+ region="Unknown",
192
+ account_id="Unknown",
193
+ resource_id="Unknown",
194
+ actual_value=f"Check execution failed: {str(e)}",
195
+ remediation="Check logs for more details"
196
+ )
197
+ )
@@ -0,0 +1,3 @@
1
+ """
2
+ Package containing AWS Config security checks.
3
+ """
@@ -0,0 +1,3 @@
1
+ """
2
+ Core components for the SRA Verify framework.
3
+ """
@@ -0,0 +1,227 @@
1
+ """
2
+ Base class for security checks.
3
+ """
4
+ from typing import List, Optional, Dict, Any
5
+ import boto3
6
+ from sraverify.core.logging import logger
7
+
8
+
9
+ class SecurityCheck:
10
+ """Base class for all security checks."""
11
+
12
+ # Class-level cache for account information shared across all instances
13
+ _account_info_cache = {}
14
+
15
+ def __init__(self, account_type="application", service=None, resource_type=None):
16
+ """
17
+ Initialize security check.
18
+
19
+ Args:
20
+ account_type: Type of account (application, audit, log-archive, management)
21
+ service: AWS service name
22
+ resource_type: AWS resource type for findings
23
+ """
24
+ self.account_type = account_type
25
+ self.service = service
26
+ self.resource_type = resource_type
27
+ self.check_id = None
28
+ self.check_name = None
29
+ self.description = None
30
+ self.rationale = None
31
+ self.remediation = None
32
+ self.severity = "Unknown"
33
+ self.check_logic = None
34
+ self.findings = []
35
+ self.regions = []
36
+ self.session = None
37
+ self._clients = {}
38
+ self.account_info = None # Will hold {'account_id': str, 'account_name': str}
39
+
40
+ def initialize(self, session: boto3.Session, regions: Optional[List[str]] = None):
41
+ """
42
+ Initialize check with AWS session and optional regions.
43
+
44
+ Args:
45
+ session: AWS session to use for the check
46
+ regions: List of AWS regions to check. If not provided, enabled regions will be detected.
47
+ """
48
+ logger.debug(f"Initializing {self.__class__.__name__} check")
49
+ self.session = session
50
+ # All account types need regions, so we'll get them regardless of account type
51
+ self.regions = regions if regions else self._get_enabled_regions()
52
+ logger.debug(f"Check will run in regions: {', '.join(self.regions)}")
53
+
54
+ # Get account info once during initialization
55
+ self.account_info = self._get_account_info()
56
+ logger.debug(f"Check initialized for account: {self.account_info['account_name']} ({self.account_info['account_id']})")
57
+
58
+ self._setup_clients()
59
+
60
+ def _get_enabled_regions(self) -> List[str]:
61
+ """
62
+ Get all enabled regions in the AWS account.
63
+
64
+ Returns:
65
+ List of enabled region names
66
+ """
67
+ try:
68
+ logger.debug("Getting enabled AWS regions")
69
+ session = boto3.Session()
70
+ ec2_client = session.client('ec2', region_name='us-east-1')
71
+ response = ec2_client.describe_regions(AllRegions=False)
72
+ regions = [region['RegionName'] for region in response['Regions']]
73
+ logger.debug(f"Found {len(regions)} enabled regions")
74
+ return regions
75
+ except Exception as e:
76
+ logger.error(f"Failed to get enabled regions: {str(e)}")
77
+ raise Exception(f"Failed to get enabled regions: {str(e)}")
78
+
79
+ def _setup_clients(self):
80
+ """
81
+ Set up clients for each region. Must be implemented by subclasses.
82
+ """
83
+ raise NotImplementedError("Subclasses must implement _setup_clients method")
84
+
85
+ def get_client(self, region: str) -> Optional[Any]:
86
+ """
87
+ Get client for a specific region.
88
+
89
+ Args:
90
+ region: AWS region name
91
+
92
+ Returns:
93
+ Client for the region or None if not available
94
+ """
95
+ return self._clients.get(region)
96
+
97
+ def create_finding(self, status: str, region: str, resource_id: str,
98
+ actual_value: str, remediation: str,
99
+ checked_value: Optional[str] = None) -> Dict[str, Any]:
100
+ """
101
+ Create a standardized finding.
102
+
103
+ Args:
104
+ status: Check status (PASS/FAIL/ERROR)
105
+ region: AWS region
106
+ resource_id: Resource identifier
107
+ actual_value: Actual value found
108
+ remediation: Remediation steps
109
+ checked_value: Value that was checked (defaults to service name + " Configuration")
110
+
111
+ Returns:
112
+ Finding dictionary
113
+
114
+ Note: account_id and account_name are automatically populated from initialization.
115
+ """
116
+ if checked_value is None:
117
+ checked_value = f"{self.service} Configuration"
118
+
119
+ return {
120
+ "CheckId": self.check_id,
121
+ "Status": status,
122
+ "Region": region,
123
+ "Severity": self.severity,
124
+ "Title": f"{self.check_id} {self.check_name}",
125
+ "Description": self.description,
126
+ "ResourceId": resource_id,
127
+ "ResourceType": self.resource_type,
128
+ "AccountId": self.account_id,
129
+ "AccountName": self.account_name,
130
+ "CheckedValue": checked_value,
131
+ "ActualValue": actual_value,
132
+ "Remediation": remediation,
133
+ "Service": self.service,
134
+ "CheckLogic": self.check_logic,
135
+ "AccountType": self.account_type
136
+ }
137
+
138
+ def execute(self) -> List[Dict[str, Any]]:
139
+ """
140
+ Execute the check. Must be implemented by subclasses.
141
+
142
+ Returns:
143
+ List of findings
144
+ """
145
+ raise NotImplementedError("Subclasses must implement execute method")
146
+
147
+ def get_findings(self) -> List[Dict[str, Any]]:
148
+ """
149
+ Get findings from the check.
150
+
151
+ Returns:
152
+ List of findings
153
+ """
154
+ return self.findings
155
+
156
+ def _get_account_info(self) -> Dict[str, str]:
157
+ """
158
+ Get account ID and name from AWS Account API with caching.
159
+
160
+ Returns:
161
+ Dictionary with 'account_id' and 'account_name' keys
162
+ """
163
+ # Get account ID from STS first (reliable, high rate limits)
164
+ try:
165
+ sts_client = self.session.client("sts")
166
+ response = sts_client.get_caller_identity()
167
+ account_id = response["Account"]
168
+ except Exception as e:
169
+ logger.error(f"Failed to get account ID from STS: {str(e)}")
170
+ raise Exception(f"Failed to get account ID: {str(e)}")
171
+
172
+ # Check class-level cache
173
+ if account_id in SecurityCheck._account_info_cache:
174
+ logger.debug(f"Using cached account information for {account_id}")
175
+ return SecurityCheck._account_info_cache[account_id]
176
+
177
+ # Try to get account name from Account API (low rate limits)
178
+ try:
179
+ logger.debug("Getting AWS account name from Account API")
180
+ account_client = self.session.client("account")
181
+ response = account_client.get_account_information()
182
+ account_name = response['AccountName']
183
+ logger.debug(f"Retrieved account name: {account_name}")
184
+ except Exception as e:
185
+ logger.warning(f"Failed to get account name from Account API: {str(e)}")
186
+ account_name = "" # Blank account name when Account API fails
187
+
188
+ # Create and cache account info
189
+ account_info = {
190
+ 'account_id': account_id,
191
+ 'account_name': account_name
192
+ }
193
+ SecurityCheck._account_info_cache[account_id] = account_info
194
+ logger.debug(f"Cached account information for {account_id}")
195
+
196
+ return account_info
197
+
198
+ @property
199
+ def account_id(self) -> str:
200
+ """Get current account ID."""
201
+ return self.account_info['account_id'] if self.account_info else None
202
+
203
+ @property
204
+ def account_name(self) -> str:
205
+ """Get current account name."""
206
+ return self.account_info['account_name'] if self.account_info else None
207
+
208
+ def get_management_accountId(self, session: boto3.Session) -> str:
209
+ """
210
+ Get AWS management account ID from the session.
211
+
212
+ Args:
213
+ session: AWS session
214
+
215
+ Returns:
216
+ AWS management account ID
217
+ """
218
+ try:
219
+ logger.debug("Getting AWS management account ID")
220
+ org_client = session.client("organizations")
221
+ response = org_client.describe_organization()
222
+ management_account_id = response["Organization"]["MasterAccountId"]
223
+ logger.debug(f"Management account ID: {management_account_id}")
224
+ return management_account_id
225
+ except Exception as e:
226
+ logger.error(f"Failed to get AWS management account ID: {str(e)}")
227
+ raise Exception(f"Failed to get AWS management account ID: {str(e)}")