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,103 @@
1
+ """
2
+ Check if IAM Access Analyzer delegated admin is the Audit account.
3
+ """
4
+ from typing import Dict, List, Any
5
+ from sraverify.services.accessanalyzer.base import AccessAnalyzerCheck
6
+ from sraverify.core.logging import logger
7
+
8
+
9
+ class SRA_ACCESSANALYZER_03(AccessAnalyzerCheck):
10
+ """Check if IAM Access Analyzer delegated admin is the Audit account."""
11
+
12
+ def __init__(self):
13
+ """Initialize IAM Access Analyzer check."""
14
+ super().__init__()
15
+ self.check_id = "SRA-ACCESSANALYZER-03"
16
+ self.check_name = "IAM Access Analyzer Delegated Admin is the Audit Account"
17
+ self.description = ("This check verifies whether IAA delegated admin account is the "
18
+ "audit account of your AWS organization. Audit account is "
19
+ "dedicated to operating security services, monitoring AWS accounts, and "
20
+ "automating security alerting and response. IAA helps monitor resources "
21
+ "shared outside zone of trust.")
22
+ self.severity = "HIGH"
23
+ self.account_type = "management"
24
+ self.check_logic = ("Check if the delegated administrator account matches any of the specified Audit account IDs")
25
+
26
+ def execute(self) -> List[Dict[str, Any]]:
27
+ """Execute the check."""
28
+ findings = []
29
+
30
+ delegated_admin = self.get_delegated_admin()
31
+
32
+ if not delegated_admin:
33
+ findings.append(
34
+ self.create_finding(
35
+ status="FAIL",
36
+ region="global",
37
+ resource_id=f"organization/{self.account_id}",
38
+ actual_value="No delegated administrator configured for IAM Access Analyzer",
39
+ remediation="Configure a delegated administrator for IAM Access Analyzer first"
40
+ )
41
+ )
42
+ return findings
43
+
44
+ try:
45
+ # Get the delegated admin account ID
46
+ delegated_admin_id = delegated_admin.get('Id')
47
+
48
+ # Check if audit_accounts is provided via _audit_accounts (new attribute name)
49
+ audit_accounts = []
50
+ if hasattr(self, '_audit_accounts') and self._audit_accounts:
51
+ audit_accounts = self._audit_accounts
52
+ # For backward compatibility, also check the old attribute name
53
+ elif hasattr(self, 'audit_accounts') and self.audit_accounts:
54
+ audit_accounts = self.audit_accounts
55
+
56
+ if not audit_accounts:
57
+ findings.append(
58
+ self.create_finding(
59
+ status="ERROR",
60
+ region="global",
61
+ resource_id=delegated_admin_id,
62
+ actual_value="Audit Account ID not provided",
63
+ remediation="Provide the Audit account IDs using --audit-account flag"
64
+ )
65
+ )
66
+ return findings
67
+
68
+ # Check if delegated admin matches any of the specified Audit accounts
69
+ if delegated_admin_id in audit_accounts:
70
+ findings.append(
71
+ self.create_finding(
72
+ status="PASS",
73
+ region="global",
74
+ resource_id=delegated_admin_id,
75
+ actual_value=f"IAM Access Analyzer delegated administrator (Account: {delegated_admin_id}) "
76
+ f"matches one of the specified Audit accounts {', '.join(audit_accounts)}",
77
+ remediation="No remediation needed"
78
+ )
79
+ )
80
+ else:
81
+ findings.append(
82
+ self.create_finding(
83
+ status="FAIL",
84
+ region="global",
85
+ resource_id=delegated_admin_id,
86
+ actual_value=f"IAM Access Analyzer delegated administrator (Account: {delegated_admin_id}) "
87
+ f"does not match any of the specified Audit accounts ({', '.join(audit_accounts)})",
88
+ remediation=f"Update the delegated administrator to be one of the Audit accounts ({', '.join(audit_accounts)})"
89
+ )
90
+ )
91
+
92
+ except Exception as e:
93
+ findings.append(
94
+ self.create_finding(
95
+ status="ERROR",
96
+ region="global",
97
+ resource_id=delegated_admin_id if 'delegated_admin_id' in locals() else f"organization/{self.account_id}",
98
+ actual_value=f"Error checking delegated administrator: {str(e)}",
99
+ remediation="Ensure proper permissions to check Organizations structure"
100
+ )
101
+ )
102
+
103
+ return findings
@@ -0,0 +1,139 @@
1
+ """
2
+ Check if IAM Access Analyzer external access analyzer is configured with Organization zone of trust in every region.
3
+ """
4
+ from typing import Dict, List, Any
5
+ from sraverify.services.accessanalyzer.base import AccessAnalyzerCheck
6
+ from sraverify.core.logging import logger
7
+
8
+
9
+ class SRA_ACCESSANALYZER_04(AccessAnalyzerCheck):
10
+ """Check if IAM Access Analyzer has an analyzer with Organization zone of trust in every region."""
11
+
12
+ def __init__(self):
13
+ """Initialize IAM Access Analyzer check."""
14
+ super().__init__()
15
+ self.check_id = "SRA-ACCESSANALYZER-04"
16
+ self.check_name = "IAM Access Analyzer external access analyzer is configured with Organization zone of trust in every region"
17
+ self.description = ("This check verifies whether IAA external access analyzer is configured with a zone of trust "
18
+ "of your AWS organization in every available region. IAM Access Analyzer generates a finding for each instance of a "
19
+ "resource-based policy that grants access to a resource within your zone of trust to a "
20
+ "principal that is not within your zone of trust. When you configure an organization as the "
21
+ "zone of trust for an analyzer- IAA generates findings or each instance of a resource-based "
22
+ "policy that grants access to a resource within your AWS organization to a principal that is "
23
+ "not within your AWS organization.")
24
+ self.severity = "HIGH"
25
+ self.account_type = "audit"
26
+ self.check_logic = ("Check if an IAM Access Analyzer with Organization zone of trust exists in each region, created by the audit account")
27
+ self._analyzer_details_cache = {}
28
+ self._audit_accounts = []
29
+
30
+ def execute(self) -> List[Dict[str, Any]]:
31
+ """Execute the check for each region."""
32
+ findings = []
33
+ # First, verify this check is running from an audit account - silently add to findings without logging warnings
34
+ if hasattr(self, '_audit_accounts') and self._audit_accounts:
35
+ if self.account_id not in self._audit_accounts:
36
+ # Don't log a warning, just add to findings
37
+ findings.append(
38
+ self.create_finding(
39
+ status="ERROR",
40
+ region="global",
41
+ resource_id="accessanalyzer:account-validation",
42
+ actual_value=f"Invalid account for IAM Access Analyzer check: Account {self.account_id} is not an audit account",
43
+ remediation=f"This check must be run from an audit account ({', '.join(self._audit_accounts)}). Either run this check from one of the designated audit accounts or update your configuration to specify the correct audit account(s) using the --account-type parameter."
44
+ )
45
+ )
46
+ return findings
47
+
48
+ # If no regions have Access Analyzer available, return a single error
49
+ if not self._clients:
50
+ findings.append(
51
+ self.create_finding(
52
+ status="ERROR",
53
+ region="global",
54
+ resource_id=f"accessanalyzer:global",
55
+ actual_value="IAM Access Analyzer not available in any specified region",
56
+ remediation="Ensure IAM Access Analyzer service is available in at least one region and you have proper permissions"
57
+ )
58
+ )
59
+ return findings
60
+
61
+ # Check if any analyzers exist across all regions
62
+ total_analyzers = 0
63
+ for region, client in self._clients.items():
64
+ analyzers = self.get_analyzers(region)
65
+ total_analyzers += len(analyzers)
66
+
67
+ # If no analyzers exist at all, return a single global finding
68
+ if total_analyzers == 0:
69
+ findings.append(
70
+ self.create_finding(
71
+ status="FAIL",
72
+ region="global",
73
+ resource_id="accessanalyzer:global",
74
+ actual_value="No IAM Access Analyzers found in any region",
75
+ remediation=(
76
+ "Create IAM Access Analyzers with Organization zone of trust in each region using the AWS CLI command: "
77
+ "aws accessanalyzer create-analyzer --analyzer-name org-analyzer --type ORGANIZATION --region <region>"
78
+ )
79
+ )
80
+ )
81
+ return findings
82
+
83
+ # Track if we found organization analyzers in any region
84
+ found_org_analyzers = False
85
+ all_regions_checked = True
86
+
87
+ # Check each region where Access Analyzer is available
88
+ for region, client in self._clients.items():
89
+ try:
90
+ # Get analyzers for this region using the base class method that handles caching
91
+ analyzers = self.get_analyzers(region)
92
+
93
+ # Look for analyzers with organization zone of trust in this specific region
94
+ # that are created by this account
95
+ org_analyzers = [
96
+ a for a in analyzers
97
+ if a.get('type') == 'ORGANIZATION'
98
+ and a.get('status') == 'ACTIVE'
99
+ and a.get('arn', '').split(':')[4] == self.account_id
100
+ ]
101
+
102
+ if org_analyzers:
103
+ # If we found organization analyzers in this region created by this account, report a PASS
104
+ found_org_analyzers = True
105
+ analyzer_names = [a.get('name', 'Unknown') for a in org_analyzers]
106
+ analyzer_arn = org_analyzers[0].get('arn', f"arn:aws:access-analyzer:{region}:{self.account_id}:analyzer/{region}")
107
+
108
+ findings.append(
109
+ self.create_finding(
110
+ status="PASS",
111
+ region=region,
112
+ resource_id=analyzer_arn,
113
+ actual_value=f"Found IAM Access Analyzer with Organization zone of trust in {region}: {', '.join(analyzer_names)}",
114
+ remediation="No remediation needed"
115
+ )
116
+ )
117
+ else:
118
+ # If no organization analyzers in this region created by this account
119
+ findings.append(
120
+ self.create_finding(
121
+ status="FAIL",
122
+ region=region,
123
+ resource_id="No Organization analyzer found in this region",
124
+ actual_value=f"No IAM Access Analyzer with Organization zone of trust found in {region}",
125
+ remediation=f"Create an IAM Access Analyzer with Organization zone of trust in {region} using the AWS CLI command: aws accessanalyzer create-analyzer --analyzer-name org-analyzer --type ORGANIZATION --region {region}"
126
+ )
127
+ )
128
+ except Exception as e:
129
+ all_regions_checked = False
130
+ findings.append(
131
+ self.create_finding(
132
+ status="ERROR",
133
+ region=region, resource_id="error",
134
+ actual_value=f"Error checking IAM Access Analyzer in {region}: {str(e)}",
135
+ remediation="Ensure you have proper permissions to list IAM Access Analyzers and that the service is available in this region"
136
+ )
137
+ )
138
+
139
+ return findings
@@ -0,0 +1,123 @@
1
+ """
2
+ IAM Access Analyzer client for interacting with AWS IAM Access Analyzer service.
3
+ """
4
+ from typing import Dict, List, Optional, Any
5
+ import boto3
6
+ from botocore.exceptions import ClientError, EndpointConnectionError
7
+ from sraverify.core.logging import logger
8
+
9
+
10
+ class AccessAnalyzerClient:
11
+ """Client for interacting with AWS IAM Access Analyzer service."""
12
+
13
+ def __init__(self, region: str, session: Optional[boto3.Session] = None):
14
+ """
15
+ Initialize IAM Access Analyzer 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('accessanalyzer', region_name=region)
24
+ self.org_client = self.session.client('organizations', region_name=region)
25
+ logger.debug(f"Initialized AccessAnalyzerClient for region {region}")
26
+
27
+ def is_access_analyzer_available(self) -> bool:
28
+ """Check if Access Analyzer is available in the region."""
29
+ try:
30
+ logger.debug(f"Checking if Access Analyzer is available in {self.region}")
31
+ self.client.list_analyzers(maxResults=1)
32
+ logger.debug(f"Access Analyzer is available in {self.region}")
33
+ return True
34
+ except ClientError as e:
35
+ error_code = e.response.get('Error', {}).get('Code', '')
36
+ if error_code == 'AccessDeniedException':
37
+ # If we get an access denied, the service exists but we don't have permissions
38
+ logger.debug(f"Access Analyzer exists in {self.region} but access is denied")
39
+ return True
40
+ logger.debug(f"Access Analyzer not available in {self.region}: {error_code}")
41
+ return False
42
+ except EndpointConnectionError:
43
+ # Service not available in this region
44
+ logger.debug(f"Access Analyzer endpoint not available in {self.region}")
45
+ return False
46
+ except Exception as e:
47
+ # Any other error, assume service is not available
48
+ logger.debug(f"Error checking Access Analyzer availability in {self.region}: {str(e)}")
49
+ return False
50
+
51
+ def list_analyzers(self) -> List[Dict[str, Any]]:
52
+ """
53
+ List all analyzers in the region.
54
+
55
+ Returns:
56
+ List of analyzer details
57
+ """
58
+ try:
59
+ analyzers = []
60
+ paginator = self.client.get_paginator('list_analyzers')
61
+
62
+ logger.debug(f"Listing analyzers in {self.region}")
63
+ for page in paginator.paginate():
64
+ analyzers.extend(page.get('analyzers', []))
65
+
66
+ logger.debug(f"Found {len(analyzers)} analyzers in {self.region}")
67
+ return analyzers
68
+ except ClientError as e:
69
+ logger.warning(f"Error listing analyzers in {self.region}: {e}")
70
+ return []
71
+ except Exception as e:
72
+ logger.warning(f"Unexpected error listing analyzers in {self.region}: {e}")
73
+ return []
74
+
75
+ def get_analyzer_details(self, analyzer_arn: str) -> Dict[str, Any]:
76
+ """
77
+ Get details for a specific analyzer.
78
+
79
+ Args:
80
+ analyzer_arn: ARN of the analyzer
81
+
82
+ Returns:
83
+ Analyzer details
84
+ """
85
+ try:
86
+ logger.debug(f"Getting details for analyzer {analyzer_arn}")
87
+ response = self.client.get_analyzer(analyzerArn=analyzer_arn)
88
+ return response
89
+ except ClientError as e:
90
+ logger.warning(f"Error getting analyzer details for {analyzer_arn}: {e}")
91
+ return {}
92
+ except Exception as e:
93
+ logger.warning(f"Unexpected error getting analyzer details for {analyzer_arn}: {e}")
94
+ return {}
95
+
96
+ def get_delegated_admin(self) -> Dict[str, Any]:
97
+ """
98
+ Get the delegated administrator for IAM Access Analyzer.
99
+
100
+ Returns:
101
+ Dictionary containing delegated administrator details or empty dict if none
102
+ """
103
+ try:
104
+ logger.debug("Getting delegated administrator for IAM Access Analyzer")
105
+ response = self.org_client.list_delegated_administrators(ServicePrincipal='access-analyzer.amazonaws.com')
106
+ delegated_admins = response.get('DelegatedAdministrators', [])
107
+
108
+ if delegated_admins:
109
+ logger.debug(f"Found delegated administrator for IAM Access Analyzer: {delegated_admins[0].get('Id')}")
110
+ return delegated_admins[0]
111
+ else:
112
+ logger.debug("No delegated administrator found for IAM Access Analyzer")
113
+ return {}
114
+ except ClientError as e:
115
+ error_code = e.response.get('Error', {}).get('Code', '')
116
+ if error_code == 'AWSOrganizationsNotInUseException':
117
+ logger.debug("AWS Organizations not in use")
118
+ else:
119
+ logger.warning(f"Error getting delegated administrator: {e}")
120
+ return {}
121
+ except Exception as e:
122
+ logger.warning(f"Unexpected error getting delegated administrator: {e}")
123
+ return {}
@@ -0,0 +1,9 @@
1
+ from sraverify.services.account.checks.sra_account_01 import SRA_ACCOUNT_01
2
+ from sraverify.services.account.checks.sra_account_02 import SRA_ACCOUNT_02
3
+ from sraverify.services.account.checks.sra_account_03 import SRA_ACCOUNT_03
4
+
5
+ CHECKS = {
6
+ "SRA-ACCOUNT-01": SRA_ACCOUNT_01,
7
+ "SRA-ACCOUNT-02": SRA_ACCOUNT_02,
8
+ "SRA-ACCOUNT-03": SRA_ACCOUNT_03,
9
+ }
@@ -0,0 +1,56 @@
1
+ """
2
+ Base class for Account security checks.
3
+ """
4
+ from typing import Dict, Any
5
+ from sraverify.core.check import SecurityCheck
6
+ from sraverify.services.account.client import AccountClient
7
+ from sraverify.core.logging import logger
8
+
9
+
10
+ class AccountCheck(SecurityCheck):
11
+ """Base class for all Account security checks."""
12
+
13
+ # Class-level cache shared across all instances
14
+ _contact_cache = {}
15
+
16
+ def __init__(self):
17
+ """Initialize Account base check."""
18
+ super().__init__(
19
+ account_type="application",
20
+ service="Account",
21
+ resource_type="AWS::Account::AlternateContact"
22
+ )
23
+
24
+ def _setup_clients(self):
25
+ """Set up Account clients for each region."""
26
+ self._clients.clear()
27
+ if hasattr(self, 'regions') and self.regions:
28
+ for region in self.regions:
29
+ self._clients[region] = AccountClient(region, session=self.session)
30
+
31
+ def get_alternate_contact(self, region: str, contact_type: str, account_id: str = None) -> Dict[str, Any]:
32
+ """
33
+ Get alternate contact information with caching.
34
+
35
+ Args:
36
+ region: AWS region name
37
+ contact_type: Type of contact (BILLING, OPERATIONS, or SECURITY)
38
+ account_id: Optional account ID
39
+
40
+ Returns:
41
+ Dictionary containing contact details or empty dict if not available
42
+ """
43
+ cache_key = f"{self.account_id}:{region}:{contact_type}"
44
+ if cache_key in AccountCheck._contact_cache:
45
+ logger.debug(f"Account: Using cached {contact_type} contact for {region}")
46
+ return AccountCheck._contact_cache[cache_key]
47
+
48
+ client = self.get_client(region)
49
+ if not client:
50
+ logger.warning(f"Account: No Account client available for region {region}")
51
+ return {}
52
+
53
+ contact_info = client.get_alternate_contact(contact_type, account_id)
54
+ AccountCheck._contact_cache[cache_key] = contact_info
55
+
56
+ return contact_info
@@ -0,0 +1 @@
1
+ # Account checks module
@@ -0,0 +1,65 @@
1
+ """
2
+ SRA-ACCOUNT-01: Verify security alternate contact is configured
3
+ """
4
+ from typing import Dict, List, Any
5
+ from sraverify.services.account.base import AccountCheck
6
+
7
+
8
+ class SRA_ACCOUNT_01(AccountCheck):
9
+ """Check if security alternate contact is configured for the AWS account."""
10
+
11
+ def __init__(self):
12
+ super().__init__()
13
+ self.check_id = "SRA-ACCOUNT-01"
14
+ self.check_name = "Security alternate contact configured"
15
+ self.description = "Verifies that a security alternate contact is configured for the AWS account"
16
+ self.severity = "MEDIUM"
17
+ self.check_logic = "Uses GetAlternateContact API to verify security contact exists and has required fields"
18
+
19
+ def execute(self) -> List[Dict[str, Any]]:
20
+ """Execute the security alternate contact check."""
21
+ account_id = self.account_id
22
+
23
+ # Account-level check only needs to run once, use first region
24
+ region = self.regions[0] if self.regions else "us-east-1"
25
+
26
+ contact_info = self.get_alternate_contact(region, "SECURITY")
27
+
28
+ if "Error" in contact_info:
29
+ error_code = contact_info["Error"].get("Code", "")
30
+ if error_code == "ResourceNotFoundException":
31
+ self.findings.append(self.create_finding(
32
+ status="FAIL",
33
+ region=region,
34
+ resource_id=f"account-{account_id}",
35
+ actual_value="No security alternate contact configured",
36
+ remediation="Configure a security alternate contact using AWS Console > Account Settings > Alternate contacts or AWS CLI: aws account put-alternate-contact --alternate-contact-type SECURITY --email-address <email> --name <name> --phone-number <phone> --title <title>"
37
+ ))
38
+ else:
39
+ self.findings.append(self.create_finding(
40
+ status="ERROR",
41
+ region=region,
42
+ resource_id=f"account-{account_id}",
43
+ actual_value=contact_info["Error"].get("Message", "Unknown error"),
44
+ remediation="Check IAM permissions for Account Management API access"
45
+ ))
46
+ else:
47
+ contact = contact_info.get("AlternateContact", {})
48
+ if contact and contact.get("EmailAddress") and contact.get("Name"):
49
+ self.findings.append(self.create_finding(
50
+ status="PASS",
51
+ region=region,
52
+ resource_id=f"account-{account_id}",
53
+ actual_value=f"Security contact configured: {contact.get('Name')} ({contact.get('EmailAddress')})",
54
+ remediation="No remediation needed"
55
+ ))
56
+ else:
57
+ self.findings.append(self.create_finding(
58
+ status="FAIL",
59
+ region=region,
60
+ resource_id=f"account-{account_id}",
61
+ actual_value="Security alternate contact exists but missing required fields",
62
+ remediation="Update security alternate contact to include name and email address using AWS Console > Account Settings > Alternate contacts"
63
+ ))
64
+
65
+ return self.findings
@@ -0,0 +1,63 @@
1
+ """
2
+ SRA-ACCOUNT-02: Verify billing alternate contact is configured
3
+ """
4
+ from typing import Dict, List, Any
5
+ from sraverify.services.account.base import AccountCheck
6
+
7
+
8
+ class SRA_ACCOUNT_02(AccountCheck):
9
+ """Check if billing alternate contact is configured for the AWS account."""
10
+
11
+ def __init__(self):
12
+ super().__init__()
13
+ self.check_id = "SRA-ACCOUNT-02"
14
+ self.check_name = "Billing alternate contact configured"
15
+ self.description = "Verifies that a billing alternate contact is configured for the AWS account"
16
+ self.severity = "MEDIUM"
17
+ self.check_logic = "Uses GetAlternateContact API to verify billing contact exists and has required fields"
18
+
19
+ def execute(self) -> List[Dict[str, Any]]:
20
+ """Execute the billing alternate contact check."""
21
+ account_id = self.account_id
22
+ region = self.regions[0] if self.regions else "us-east-1"
23
+
24
+ contact_info = self.get_alternate_contact(region, "BILLING")
25
+
26
+ if "Error" in contact_info:
27
+ error_code = contact_info["Error"].get("Code", "")
28
+ if error_code == "ResourceNotFoundException":
29
+ self.findings.append(self.create_finding(
30
+ status="FAIL",
31
+ region=region,
32
+ resource_id=f"account-{account_id}",
33
+ actual_value="No billing alternate contact configured",
34
+ remediation="Configure a billing alternate contact using AWS Console > Account Settings > Alternate contacts or AWS CLI: aws account put-alternate-contact --alternate-contact-type BILLING --email-address <email> --name <name> --phone-number <phone> --title <title>"
35
+ ))
36
+ else:
37
+ self.findings.append(self.create_finding(
38
+ status="ERROR",
39
+ region=region,
40
+ resource_id=f"account-{account_id}",
41
+ actual_value=contact_info["Error"].get("Message", "Unknown error"),
42
+ remediation="Check IAM permissions for Account Management API access"
43
+ ))
44
+ else:
45
+ contact = contact_info.get("AlternateContact", {})
46
+ if contact and contact.get("EmailAddress") and contact.get("Name"):
47
+ self.findings.append(self.create_finding(
48
+ status="PASS",
49
+ region=region,
50
+ resource_id=f"account-{account_id}",
51
+ actual_value=f"Billing contact configured: {contact.get('Name')} ({contact.get('EmailAddress')})",
52
+ remediation="No remediation needed"
53
+ ))
54
+ else:
55
+ self.findings.append(self.create_finding(
56
+ status="FAIL",
57
+ region=region,
58
+ resource_id=f"account-{account_id}",
59
+ actual_value="Billing alternate contact exists but missing required fields",
60
+ remediation="Update billing alternate contact to include name and email address using AWS Console > Account Settings > Alternate contacts"
61
+ ))
62
+
63
+ return self.findings
@@ -0,0 +1,63 @@
1
+ """
2
+ SRA-ACCOUNT-03: Verify operations alternate contact is configured
3
+ """
4
+ from typing import Dict, List, Any
5
+ from sraverify.services.account.base import AccountCheck
6
+
7
+
8
+ class SRA_ACCOUNT_03(AccountCheck):
9
+ """Check if operations alternate contact is configured for the AWS account."""
10
+
11
+ def __init__(self):
12
+ super().__init__()
13
+ self.check_id = "SRA-ACCOUNT-03"
14
+ self.check_name = "Operations alternate contact configured"
15
+ self.description = "Verifies that an operations alternate contact is configured for the AWS account"
16
+ self.severity = "MEDIUM"
17
+ self.check_logic = "Uses GetAlternateContact API to verify operations contact exists and has required fields"
18
+
19
+ def execute(self) -> List[Dict[str, Any]]:
20
+ """Execute the operations alternate contact check."""
21
+ account_id = self.account_id
22
+ region = self.regions[0] if self.regions else "us-east-1"
23
+
24
+ contact_info = self.get_alternate_contact(region, "OPERATIONS")
25
+
26
+ if "Error" in contact_info:
27
+ error_code = contact_info["Error"].get("Code", "")
28
+ if error_code == "ResourceNotFoundException":
29
+ self.findings.append(self.create_finding(
30
+ status="FAIL",
31
+ region=region,
32
+ resource_id=f"account-{account_id}",
33
+ actual_value="No operations alternate contact configured",
34
+ remediation="Configure an operations alternate contact using AWS Console > Account Settings > Alternate contacts or AWS CLI: aws account put-alternate-contact --alternate-contact-type OPERATIONS --email-address <email> --name <name> --phone-number <phone> --title <title>"
35
+ ))
36
+ else:
37
+ self.findings.append(self.create_finding(
38
+ status="ERROR",
39
+ region=region,
40
+ resource_id=f"account-{account_id}",
41
+ actual_value=contact_info["Error"].get("Message", "Unknown error"),
42
+ remediation="Check IAM permissions for Account Management API access"
43
+ ))
44
+ else:
45
+ contact = contact_info.get("AlternateContact", {})
46
+ if contact and contact.get("EmailAddress") and contact.get("Name"):
47
+ self.findings.append(self.create_finding(
48
+ status="PASS",
49
+ region=region,
50
+ resource_id=f"account-{account_id}",
51
+ actual_value=f"Operations contact configured: {contact.get('Name')} ({contact.get('EmailAddress')})",
52
+ remediation="No remediation needed"
53
+ ))
54
+ else:
55
+ self.findings.append(self.create_finding(
56
+ status="FAIL",
57
+ region=region,
58
+ resource_id=f"account-{account_id}",
59
+ actual_value="Operations alternate contact exists but missing required fields",
60
+ remediation="Update operations alternate contact to include name and email address using AWS Console > Account Settings > Alternate contacts"
61
+ ))
62
+
63
+ return self.findings