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