sraverify 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sraverify/__init__.py +36 -0
- sraverify/checks/__init__.py +56 -0
- sraverify/checks/accessanalyzer/SRA_IAA_1.py +188 -0
- sraverify/checks/accessanalyzer/SRA_IAA_2.py +162 -0
- sraverify/checks/accessanalyzer/SRA_IAA_3.py +260 -0
- sraverify/checks/accessanalyzer/SRA_IAA_4.py +207 -0
- sraverify/checks/accessanalyzer/__init__.py +3 -0
- sraverify/checks/cloudtrail/SRA-CT-1.py +220 -0
- sraverify/checks/cloudtrail/SRA-CT-10.py +229 -0
- sraverify/checks/cloudtrail/SRA-CT-11.py +242 -0
- sraverify/checks/cloudtrail/SRA-CT-12.py +163 -0
- sraverify/checks/cloudtrail/SRA-CT-13.py +279 -0
- sraverify/checks/cloudtrail/SRA-CT-2.py +218 -0
- sraverify/checks/cloudtrail/SRA-CT-3.py +196 -0
- sraverify/checks/cloudtrail/SRA-CT-4.py +161 -0
- sraverify/checks/cloudtrail/SRA-CT-5.py +200 -0
- sraverify/checks/cloudtrail/SRA-CT-6.py +161 -0
- sraverify/checks/cloudtrail/SRA-CT-7.py +194 -0
- sraverify/checks/cloudtrail/SRA-CT-8.py +226 -0
- sraverify/checks/cloudtrail/SRA-CT-9.py +226 -0
- sraverify/checks/cloudtrail/__init__.py +3 -0
- sraverify/checks/config/SRA-CONFIG-1.py +197 -0
- sraverify/checks/config/__init__.py +3 -0
- sraverify/core/__init__.py +3 -0
- sraverify/core/check.py +227 -0
- sraverify/core/logging.py +37 -0
- sraverify/core/session.py +47 -0
- sraverify/lib/__init__.py +4 -0
- sraverify/lib/audit_info.py +37 -0
- sraverify/lib/banner.py +42 -0
- sraverify/lib/check_loader.py +80 -0
- sraverify/lib/org_mgmt_checker.py +86 -0
- sraverify/lib/outputs.py +46 -0
- sraverify/lib/progress.py +75 -0
- sraverify/lib/regions.py +27 -0
- sraverify/lib/session.py +23 -0
- sraverify/main.py +350 -0
- sraverify/services/__init__.py +3 -0
- sraverify/services/accessanalyzer/__init__.py +15 -0
- sraverify/services/accessanalyzer/base.py +123 -0
- sraverify/services/accessanalyzer/checks/__init__.py +3 -0
- sraverify/services/accessanalyzer/checks/sra_accessanalyzer_01.py +82 -0
- sraverify/services/accessanalyzer/checks/sra_accessanalyzer_02.py +82 -0
- sraverify/services/accessanalyzer/checks/sra_accessanalyzer_03.py +103 -0
- sraverify/services/accessanalyzer/checks/sra_accessanalyzer_04.py +139 -0
- sraverify/services/accessanalyzer/client.py +123 -0
- sraverify/services/account/__init__.py +9 -0
- sraverify/services/account/base.py +56 -0
- sraverify/services/account/checks/__init__.py +1 -0
- sraverify/services/account/checks/sra_account_01.py +65 -0
- sraverify/services/account/checks/sra_account_02.py +63 -0
- sraverify/services/account/checks/sra_account_03.py +63 -0
- sraverify/services/account/client.py +51 -0
- sraverify/services/auditmanager/__init__.py +10 -0
- sraverify/services/auditmanager/base.py +72 -0
- sraverify/services/auditmanager/checks/__init__.py +1 -0
- sraverify/services/auditmanager/checks/sra_auditmanager_01.py +58 -0
- sraverify/services/auditmanager/checks/sra_auditmanager_02.py +80 -0
- sraverify/services/auditmanager/client.py +58 -0
- sraverify/services/cloudtrail/__init__.py +33 -0
- sraverify/services/cloudtrail/base.py +167 -0
- sraverify/services/cloudtrail/checks/__init__.py +1 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_01.py +83 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_02.py +99 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_03.py +94 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_04.py +92 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_05.py +106 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_06.py +93 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_07.py +96 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_08.py +145 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_09.py +167 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_10.py +162 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_11.py +178 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_12.py +77 -0
- sraverify/services/cloudtrail/checks/sra_cloudtrail_13.py +120 -0
- sraverify/services/cloudtrail/client.py +118 -0
- sraverify/services/config/__init__.py +25 -0
- sraverify/services/config/base.py +249 -0
- sraverify/services/config/checks/__init__.py +1 -0
- sraverify/services/config/checks/sra_config_01.py +123 -0
- sraverify/services/config/checks/sra_config_02.py +156 -0
- sraverify/services/config/checks/sra_config_03.py +149 -0
- sraverify/services/config/checks/sra_config_04.py +104 -0
- sraverify/services/config/checks/sra_config_05.py +104 -0
- sraverify/services/config/checks/sra_config_06.py +194 -0
- sraverify/services/config/checks/sra_config_07.py +162 -0
- sraverify/services/config/checks/sra_config_08.py +185 -0
- sraverify/services/config/checks/sra_config_09.py +177 -0
- sraverify/services/config/client.py +264 -0
- sraverify/services/ec2/__init__.py +8 -0
- sraverify/services/ec2/base.py +75 -0
- sraverify/services/ec2/checks/__init__.py +1 -0
- sraverify/services/ec2/checks/sra_ec2_01.py +83 -0
- sraverify/services/ec2/client.py +63 -0
- sraverify/services/firewallmanager/__init__.py +23 -0
- sraverify/services/firewallmanager/base.py +48 -0
- sraverify/services/firewallmanager/checks/__init__.py +1 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_01.py +75 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_02.py +57 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_03.py +51 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_04.py +51 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_05.py +51 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_06.py +51 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_07.py +51 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_08.py +61 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_09.py +61 -0
- sraverify/services/firewallmanager/checks/sra_firewallmanager_10.py +71 -0
- sraverify/services/firewallmanager/client.py +40 -0
- sraverify/services/guardduty/__init__.py +58 -0
- sraverify/services/guardduty/base.py +207 -0
- sraverify/services/guardduty/checks/__init__.py +3 -0
- sraverify/services/guardduty/checks/sra_guardduty_01.py +51 -0
- sraverify/services/guardduty/checks/sra_guardduty_02.py +80 -0
- sraverify/services/guardduty/checks/sra_guardduty_03.py +77 -0
- sraverify/services/guardduty/checks/sra_guardduty_04.py +84 -0
- sraverify/services/guardduty/checks/sra_guardduty_05.py +84 -0
- sraverify/services/guardduty/checks/sra_guardduty_06.py +84 -0
- sraverify/services/guardduty/checks/sra_guardduty_07.py +85 -0
- sraverify/services/guardduty/checks/sra_guardduty_08.py +83 -0
- sraverify/services/guardduty/checks/sra_guardduty_09.py +84 -0
- sraverify/services/guardduty/checks/sra_guardduty_10.py +83 -0
- sraverify/services/guardduty/checks/sra_guardduty_11.py +93 -0
- sraverify/services/guardduty/checks/sra_guardduty_12.py +83 -0
- sraverify/services/guardduty/checks/sra_guardduty_13.py +90 -0
- sraverify/services/guardduty/checks/sra_guardduty_14.py +136 -0
- sraverify/services/guardduty/checks/sra_guardduty_15.py +94 -0
- sraverify/services/guardduty/checks/sra_guardduty_16.py +94 -0
- sraverify/services/guardduty/checks/sra_guardduty_17.py +91 -0
- sraverify/services/guardduty/checks/sra_guardduty_18.py +91 -0
- sraverify/services/guardduty/checks/sra_guardduty_19.py +91 -0
- sraverify/services/guardduty/checks/sra_guardduty_20.py +111 -0
- sraverify/services/guardduty/checks/sra_guardduty_21.py +112 -0
- sraverify/services/guardduty/checks/sra_guardduty_22.py +111 -0
- sraverify/services/guardduty/checks/sra_guardduty_23.py +154 -0
- sraverify/services/guardduty/checks/sra_guardduty_24.py +111 -0
- sraverify/services/guardduty/checks/sra_guardduty_25.py +111 -0
- sraverify/services/guardduty/client.py +107 -0
- sraverify/services/inspector/__init__.py +29 -0
- sraverify/services/inspector/base.py +233 -0
- sraverify/services/inspector/checks/__init__.py +3 -0
- sraverify/services/inspector/checks/sra_inspector_01.py +69 -0
- sraverify/services/inspector/checks/sra_inspector_02.py +68 -0
- sraverify/services/inspector/checks/sra_inspector_03.py +68 -0
- sraverify/services/inspector/checks/sra_inspector_04.py +70 -0
- sraverify/services/inspector/checks/sra_inspector_05.py +69 -0
- sraverify/services/inspector/checks/sra_inspector_06.py +115 -0
- sraverify/services/inspector/checks/sra_inspector_07.py +109 -0
- sraverify/services/inspector/checks/sra_inspector_08.py +69 -0
- sraverify/services/inspector/checks/sra_inspector_09.py +69 -0
- sraverify/services/inspector/checks/sra_inspector_10.py +69 -0
- sraverify/services/inspector/checks/sra_inspector_11.py +69 -0
- sraverify/services/inspector/client.py +99 -0
- sraverify/services/macie/__init__.py +27 -0
- sraverify/services/macie/base.py +271 -0
- sraverify/services/macie/checks/__init__.py +1 -0
- sraverify/services/macie/checks/sra_macie_01.py +100 -0
- sraverify/services/macie/checks/sra_macie_02.py +102 -0
- sraverify/services/macie/checks/sra_macie_03.py +152 -0
- sraverify/services/macie/checks/sra_macie_04.py +120 -0
- sraverify/services/macie/checks/sra_macie_05.py +85 -0
- sraverify/services/macie/checks/sra_macie_06.py +124 -0
- sraverify/services/macie/checks/sra_macie_07.py +138 -0
- sraverify/services/macie/checks/sra_macie_08.py +82 -0
- sraverify/services/macie/checks/sra_macie_09.py +103 -0
- sraverify/services/macie/checks/sra_macie_10.py +81 -0
- sraverify/services/macie/client.py +220 -0
- sraverify/services/s3/__init__.py +16 -0
- sraverify/services/s3/base.py +69 -0
- sraverify/services/s3/checks/__init__.py +1 -0
- sraverify/services/s3/checks/sra_s3_01.py +89 -0
- sraverify/services/s3/checks/sra_s3_02.py +89 -0
- sraverify/services/s3/checks/sra_s3_03.py +88 -0
- sraverify/services/s3/checks/sra_s3_04.py +88 -0
- sraverify/services/s3/client.py +52 -0
- sraverify/services/securityhub/__init__.py +27 -0
- sraverify/services/securityhub/base.py +349 -0
- sraverify/services/securityhub/checks/__init__.py +1 -0
- sraverify/services/securityhub/checks/sra_securityhub_01.py +115 -0
- sraverify/services/securityhub/checks/sra_securityhub_02.py +114 -0
- sraverify/services/securityhub/checks/sra_securityhub_03.py +136 -0
- sraverify/services/securityhub/checks/sra_securityhub_04.py +75 -0
- sraverify/services/securityhub/checks/sra_securityhub_05.py +102 -0
- sraverify/services/securityhub/checks/sra_securityhub_06.py +113 -0
- sraverify/services/securityhub/checks/sra_securityhub_07.py +121 -0
- sraverify/services/securityhub/checks/sra_securityhub_08.py +113 -0
- sraverify/services/securityhub/checks/sra_securityhub_09.py +100 -0
- sraverify/services/securityhub/checks/sra_securityhub_10.py +94 -0
- sraverify/services/securityhub/checks/sra_securityhub_11.py +73 -0
- sraverify/services/securityhub/client.py +249 -0
- sraverify/services/securityincidentresponse/__init__.py +13 -0
- sraverify/services/securityincidentresponse/base.py +95 -0
- sraverify/services/securityincidentresponse/checks/__init__.py +1 -0
- sraverify/services/securityincidentresponse/checks/sra_securityincidentresponse_01.py +77 -0
- sraverify/services/securityincidentresponse/checks/sra_securityincidentresponse_02.py +72 -0
- sraverify/services/securityincidentresponse/checks/sra_securityincidentresponse_03.py +86 -0
- sraverify/services/securityincidentresponse/checks/sra_securityincidentresponse_04.py +117 -0
- sraverify/services/securityincidentresponse/checks/sra_securityincidentresponse_05.py +55 -0
- sraverify/services/securityincidentresponse/client.py +71 -0
- sraverify/services/securitylake/__init__.py +39 -0
- sraverify/services/securitylake/base.py +461 -0
- sraverify/services/securitylake/checks/__init__.py +1 -0
- sraverify/services/securitylake/checks/sra_securitylake_01.py +98 -0
- sraverify/services/securitylake/checks/sra_securitylake_02.py +133 -0
- sraverify/services/securitylake/checks/sra_securitylake_03.py +116 -0
- sraverify/services/securitylake/checks/sra_securitylake_04.py +72 -0
- sraverify/services/securitylake/checks/sra_securitylake_05.py +116 -0
- sraverify/services/securitylake/checks/sra_securitylake_06.py +104 -0
- sraverify/services/securitylake/checks/sra_securitylake_07.py +108 -0
- sraverify/services/securitylake/checks/sra_securitylake_08.py +107 -0
- sraverify/services/securitylake/checks/sra_securitylake_09.py +107 -0
- sraverify/services/securitylake/checks/sra_securitylake_10.py +106 -0
- sraverify/services/securitylake/checks/sra_securitylake_11.py +109 -0
- sraverify/services/securitylake/checks/sra_securitylake_12.py +108 -0
- sraverify/services/securitylake/checks/sra_securitylake_13.py +108 -0
- sraverify/services/securitylake/checks/sra_securitylake_14.py +72 -0
- sraverify/services/securitylake/checks/sra_securitylake_15.py +120 -0
- sraverify/services/securitylake/checks/sra_securitylake_16.py +104 -0
- sraverify/services/securitylake/checks/sra_securitylake_17.py +103 -0
- sraverify/services/securitylake/client.py +247 -0
- sraverify/services/shield/__init__.py +33 -0
- sraverify/services/shield/base.py +199 -0
- sraverify/services/shield/checks/__init__.py +1 -0
- sraverify/services/shield/checks/sra_shield_01.py +68 -0
- sraverify/services/shield/checks/sra_shield_02.py +77 -0
- sraverify/services/shield/checks/sra_shield_03.py +84 -0
- sraverify/services/shield/checks/sra_shield_04.py +84 -0
- sraverify/services/shield/checks/sra_shield_05.py +84 -0
- sraverify/services/shield/checks/sra_shield_06.py +84 -0
- sraverify/services/shield/checks/sra_shield_07.py +84 -0
- sraverify/services/shield/checks/sra_shield_08.py +69 -0
- sraverify/services/shield/checks/sra_shield_09.py +86 -0
- sraverify/services/shield/checks/sra_shield_10.py +100 -0
- sraverify/services/shield/checks/sra_shield_11.py +71 -0
- sraverify/services/shield/checks/sra_shield_12.py +130 -0
- sraverify/services/shield/checks/sra_shield_13.py +112 -0
- sraverify/services/shield/checks/sra_shield_14.py +111 -0
- sraverify/services/shield/client.py +214 -0
- sraverify/services/waf/__init__.py +21 -0
- sraverify/services/waf/base.py +100 -0
- sraverify/services/waf/checks/__init__.py +1 -0
- sraverify/services/waf/checks/sra_waf_01.py +63 -0
- sraverify/services/waf/checks/sra_waf_02.py +82 -0
- sraverify/services/waf/checks/sra_waf_03.py +123 -0
- sraverify/services/waf/checks/sra_waf_04.py +94 -0
- sraverify/services/waf/checks/sra_waf_05.py +94 -0
- sraverify/services/waf/checks/sra_waf_06.py +91 -0
- sraverify/services/waf/checks/sra_waf_07.py +94 -0
- sraverify/services/waf/checks/sra_waf_08.py +66 -0
- sraverify/services/waf/checks/sra_waf_09.py +95 -0
- sraverify/services/waf/client.py +109 -0
- sraverify/utils/__init__.py +3 -0
- sraverify/utils/banner.py +65 -0
- sraverify/utils/outputs.py +57 -0
- sraverify/utils/progress.py +97 -0
- sraverify-0.1.0.dist-info/LICENSE +175 -0
- sraverify-0.1.0.dist-info/METADATA +516 -0
- sraverify-0.1.0.dist-info/NOTICE +1 -0
- sraverify-0.1.0.dist-info/RECORD +261 -0
- sraverify-0.1.0.dist-info/WHEEL +5 -0
- sraverify-0.1.0.dist-info/entry_points.txt +2 -0
- sraverify-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
from typing import Dict, List, Any
|
|
2
|
+
from sraverify.checks import SecurityCheck
|
|
3
|
+
from botocore.exceptions import ClientError
|
|
4
|
+
|
|
5
|
+
class SRAIAA3(SecurityCheck):
|
|
6
|
+
"""SRA-IAA-3: IAM Access Analyzer Security Tooling Admin"""
|
|
7
|
+
|
|
8
|
+
def __init__(self, check_type="organization", security_ou_name=None):
|
|
9
|
+
super().__init__(check_type=check_type)
|
|
10
|
+
self.check_id = "SRA-IAA-3"
|
|
11
|
+
self.check_name = "IAM Access Analyzer Security Tooling Admin"
|
|
12
|
+
self.severity = "HIGH"
|
|
13
|
+
self.security_ou_name = security_ou_name.lower() if security_ou_name else None
|
|
14
|
+
self.description = ('This check verifies whether IAA delegated admin account is the security tooling account of '
|
|
15
|
+
'your AWS organization. Security Tooling account is dedicated to operating security services + '
|
|
16
|
+
'monitoring AWS accounts + automating security alerting and response. IAA helps monitor '
|
|
17
|
+
'resources shared outside zone of trust.')
|
|
18
|
+
self.check_logic = ('1. Verify execution from Organization Management account | '
|
|
19
|
+
'2. List all Organization Units and find specified OU (via flag --security-ou-name) or OU containing "security" | '
|
|
20
|
+
'3. Create list of all accounts in Security OU | '
|
|
21
|
+
'4. List delegated administrators for IAM Access Analyzer service | '
|
|
22
|
+
'5. Verify delegated admin account exists in specified Security OU or OU containing "security" | '
|
|
23
|
+
'6. Check passes if delegated admin is found in Security OU account list')
|
|
24
|
+
self.service = 'IAM'
|
|
25
|
+
|
|
26
|
+
def get_findings(self):
|
|
27
|
+
"""Return the findings"""
|
|
28
|
+
return self.findings
|
|
29
|
+
|
|
30
|
+
def find_security_ou(self, org_client, root_id):
|
|
31
|
+
"""Find the security OU based on name parameter or default search"""
|
|
32
|
+
paginator = org_client.get_paginator('list_organizational_units_for_parent')
|
|
33
|
+
search_term = self.security_ou_name if self.security_ou_name else 'security'
|
|
34
|
+
|
|
35
|
+
for page in paginator.paginate(ParentId=root_id):
|
|
36
|
+
for ou in page['OrganizationalUnits']:
|
|
37
|
+
# If security_ou_name is provided, use exact match
|
|
38
|
+
if self.security_ou_name and self.security_ou_name == ou['Name'].lower():
|
|
39
|
+
return ou['Id'], ou['Name'], search_term
|
|
40
|
+
# If no security_ou_name provided, look for 'security' in name
|
|
41
|
+
elif not self.security_ou_name and 'security' in ou['Name'].lower():
|
|
42
|
+
return ou['Id'], ou['Name'], search_term
|
|
43
|
+
|
|
44
|
+
return None, None, search_term
|
|
45
|
+
|
|
46
|
+
def run(self, session):
|
|
47
|
+
"""Run the security check and return findings"""
|
|
48
|
+
try:
|
|
49
|
+
region = session.region_name
|
|
50
|
+
account_id = session.client('sts').get_caller_identity()['Account']
|
|
51
|
+
org_client = session.client('organizations')
|
|
52
|
+
|
|
53
|
+
# Step 1: Verify we're in management account using org_mgmt_checker
|
|
54
|
+
is_management, error_message = self.org_checker.verify_org_management()
|
|
55
|
+
if not is_management:
|
|
56
|
+
finding = {
|
|
57
|
+
'CheckId': self.check_id,
|
|
58
|
+
'Status': 'ERROR',
|
|
59
|
+
'Region': region,
|
|
60
|
+
"Severity": self.severity,
|
|
61
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
62
|
+
'Description': self.description,
|
|
63
|
+
'ResourceId': account_id,
|
|
64
|
+
'ResourceType': 'AWS::Organizations::Account',
|
|
65
|
+
'AccountId': account_id,
|
|
66
|
+
'CheckedValue': 'Management Account Access',
|
|
67
|
+
'ActualValue': error_message if error_message else 'Not running from management account',
|
|
68
|
+
'Remediation': 'Run this check from the Organization Management Account',
|
|
69
|
+
'Service': self.service,
|
|
70
|
+
'CheckLogic': self.check_logic,
|
|
71
|
+
'CheckType': self.check_type
|
|
72
|
+
}
|
|
73
|
+
self.findings.append(finding)
|
|
74
|
+
return self.findings
|
|
75
|
+
|
|
76
|
+
# Step 2 & 3: Find Security OU and list all accounts in it
|
|
77
|
+
security_ou_accounts = []
|
|
78
|
+
try:
|
|
79
|
+
root_id = org_client.list_roots()['Roots'][0]['Id']
|
|
80
|
+
|
|
81
|
+
# Find Security OU using the helper method
|
|
82
|
+
security_ou_id, ou_name, search_term = self.find_security_ou(org_client, root_id)
|
|
83
|
+
|
|
84
|
+
if not security_ou_id:
|
|
85
|
+
finding = {
|
|
86
|
+
'CheckId': self.check_id,
|
|
87
|
+
'Status': 'FAIL',
|
|
88
|
+
'Region': region,
|
|
89
|
+
"Severity": self.severity,
|
|
90
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
91
|
+
'Description': self.description,
|
|
92
|
+
'ResourceId': account_id,
|
|
93
|
+
'ResourceType': 'AWS::Organizations::OrganizationalUnit',
|
|
94
|
+
'AccountId': account_id,
|
|
95
|
+
'CheckedValue': 'Security OU',
|
|
96
|
+
'ActualValue': f'No OU matching "{search_term}" found',
|
|
97
|
+
'Remediation': f'Create appropriate OU matching "{search_term}"',
|
|
98
|
+
'Service': self.service,
|
|
99
|
+
'CheckLogic': self.check_logic,
|
|
100
|
+
'CheckType': self.check_type
|
|
101
|
+
}
|
|
102
|
+
self.findings.append(finding)
|
|
103
|
+
return self.findings
|
|
104
|
+
|
|
105
|
+
# Get all accounts in Security OU
|
|
106
|
+
accounts_paginator = org_client.get_paginator('list_accounts_for_parent')
|
|
107
|
+
for accounts_page in accounts_paginator.paginate(ParentId=security_ou_id):
|
|
108
|
+
security_ou_accounts.extend(accounts_page['Accounts'])
|
|
109
|
+
|
|
110
|
+
if not security_ou_accounts:
|
|
111
|
+
finding = {
|
|
112
|
+
'CheckId': self.check_id,
|
|
113
|
+
'Status': 'FAIL',
|
|
114
|
+
'Region': region,
|
|
115
|
+
"Severity": self.severity,
|
|
116
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
117
|
+
'Description': self.description,
|
|
118
|
+
'ResourceId': security_ou_id,
|
|
119
|
+
'ResourceType': 'AWS::Organizations::OrganizationalUnit',
|
|
120
|
+
'AccountId': account_id,
|
|
121
|
+
'CheckedValue': f'Accounts in {ou_name} OU',
|
|
122
|
+
'ActualValue': f'OU: {ou_name}, No accounts found',
|
|
123
|
+
'Remediation': f'Add accounts to {ou_name} OU',
|
|
124
|
+
'Service': self.service,
|
|
125
|
+
'CheckLogic': self.check_logic,
|
|
126
|
+
'CheckType': self.check_type
|
|
127
|
+
}
|
|
128
|
+
self.findings.append(finding)
|
|
129
|
+
return self.findings
|
|
130
|
+
|
|
131
|
+
# Step 4 & 5: Check IAA delegated administrator and verify it's in Security OU
|
|
132
|
+
try:
|
|
133
|
+
delegated_admins = org_client.list_delegated_administrators(
|
|
134
|
+
ServicePrincipal='access-analyzer.amazonaws.com'
|
|
135
|
+
).get('DelegatedAdministrators', [])
|
|
136
|
+
|
|
137
|
+
if not delegated_admins:
|
|
138
|
+
finding = {
|
|
139
|
+
'CheckId': self.check_id,
|
|
140
|
+
'Status': 'FAIL',
|
|
141
|
+
'Region': region,
|
|
142
|
+
"Severity": self.severity,
|
|
143
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
144
|
+
'Description': self.description,
|
|
145
|
+
'ResourceId': security_ou_id,
|
|
146
|
+
'ResourceType': 'AWS::Organizations::Account',
|
|
147
|
+
'AccountId': account_id,
|
|
148
|
+
'CheckedValue': 'IAA Delegated Administrator',
|
|
149
|
+
'ActualValue': f'OU: {ou_name}, No delegated administrator configured',
|
|
150
|
+
'Remediation': f'Configure an account from {ou_name} OU as IAA delegated administrator',
|
|
151
|
+
'Service': self.service,
|
|
152
|
+
'CheckLogic': self.check_logic,
|
|
153
|
+
'CheckType': self.check_type
|
|
154
|
+
}
|
|
155
|
+
self.findings.append(finding)
|
|
156
|
+
return self.findings
|
|
157
|
+
|
|
158
|
+
delegated_admin = delegated_admins[0]
|
|
159
|
+
security_ou_account_ids = [acc['Id'] for acc in security_ou_accounts]
|
|
160
|
+
|
|
161
|
+
if delegated_admin['Id'] not in security_ou_account_ids:
|
|
162
|
+
finding = {
|
|
163
|
+
'CheckId': self.check_id,
|
|
164
|
+
'Status': 'FAIL',
|
|
165
|
+
'Region': region,
|
|
166
|
+
"Severity": self.severity,
|
|
167
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
168
|
+
'Description': self.description,
|
|
169
|
+
'ResourceId': delegated_admin['Id'],
|
|
170
|
+
'ResourceType': 'AWS::Organizations::Account',
|
|
171
|
+
'AccountId': account_id,
|
|
172
|
+
'CheckedValue': f'IAA Delegated Administrator Configuration in {ou_name} OU',
|
|
173
|
+
'ActualValue': f'OU: {ou_name}, Account: {delegated_admin["Id"]} (not in OU)',
|
|
174
|
+
'Remediation': f'Configure an account from {ou_name} OU as IAA delegated administrator',
|
|
175
|
+
'Service': self.service,
|
|
176
|
+
'CheckLogic': self.check_logic,
|
|
177
|
+
'CheckType': self.check_type
|
|
178
|
+
}
|
|
179
|
+
self.findings.append(finding)
|
|
180
|
+
else:
|
|
181
|
+
finding = {
|
|
182
|
+
'CheckId': self.check_id,
|
|
183
|
+
'Status': 'PASS',
|
|
184
|
+
'Region': region,
|
|
185
|
+
"Severity": self.severity,
|
|
186
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
187
|
+
'Description': self.description,
|
|
188
|
+
'ResourceId': delegated_admin['Id'],
|
|
189
|
+
'ResourceType': 'AWS::Organizations::Account',
|
|
190
|
+
'AccountId': account_id,
|
|
191
|
+
'CheckedValue': f'IAA Delegated Administrator Configuration in {ou_name} OU',
|
|
192
|
+
'ActualValue': f'OU: {ou_name}, Account: {delegated_admin["Id"]}',
|
|
193
|
+
'Remediation': 'None required',
|
|
194
|
+
'Service': self.service,
|
|
195
|
+
'CheckLogic': self.check_logic,
|
|
196
|
+
'CheckType': self.check_type
|
|
197
|
+
}
|
|
198
|
+
self.findings.append(finding)
|
|
199
|
+
|
|
200
|
+
except ClientError as e:
|
|
201
|
+
finding = {
|
|
202
|
+
'CheckId': self.check_id,
|
|
203
|
+
'Status': 'ERROR',
|
|
204
|
+
'Region': region,
|
|
205
|
+
"Severity": self.severity,
|
|
206
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
207
|
+
'Description': self.description,
|
|
208
|
+
'ResourceId': account_id,
|
|
209
|
+
'ResourceType': 'AWS::Organizations::Account',
|
|
210
|
+
'AccountId': account_id,
|
|
211
|
+
'CheckedValue': 'Delegated Administrator Access',
|
|
212
|
+
'ActualValue': f'Error checking delegated administrator: {str(e)}',
|
|
213
|
+
'Remediation': 'Verify Organizations permissions',
|
|
214
|
+
'Service': self.service,
|
|
215
|
+
'CheckLogic': self.check_logic,
|
|
216
|
+
'CheckType': self.check_type
|
|
217
|
+
}
|
|
218
|
+
self.findings.append(finding)
|
|
219
|
+
|
|
220
|
+
except ClientError as e:
|
|
221
|
+
finding = {
|
|
222
|
+
'CheckId': self.check_id,
|
|
223
|
+
'Status': 'ERROR',
|
|
224
|
+
'Region': region,
|
|
225
|
+
"Severity": self.severity,
|
|
226
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
227
|
+
'Description': self.description,
|
|
228
|
+
'ResourceId': account_id,
|
|
229
|
+
'ResourceType': 'AWS::Organizations::OrganizationalUnit',
|
|
230
|
+
'AccountId': account_id,
|
|
231
|
+
'CheckedValue': 'Organizations Structure',
|
|
232
|
+
'ActualValue': f'Error accessing Organizations structure: {str(e)}',
|
|
233
|
+
'Remediation': 'Verify Organizations permissions and structure',
|
|
234
|
+
'Service': self.service,
|
|
235
|
+
'CheckLogic': self.check_logic,
|
|
236
|
+
'CheckType': self.check_type
|
|
237
|
+
}
|
|
238
|
+
self.findings.append(finding)
|
|
239
|
+
|
|
240
|
+
except Exception as e:
|
|
241
|
+
finding = {
|
|
242
|
+
'CheckId': self.check_id,
|
|
243
|
+
'Status': 'ERROR',
|
|
244
|
+
'Region': region,
|
|
245
|
+
"Severity": self.severity,
|
|
246
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
247
|
+
'Description': self.description,
|
|
248
|
+
'ResourceId': account_id,
|
|
249
|
+
'ResourceType': 'AWS::Organizations::Account',
|
|
250
|
+
'AccountId': account_id,
|
|
251
|
+
'CheckedValue': 'Check Execution',
|
|
252
|
+
'ActualValue': f'Error: {str(e)}',
|
|
253
|
+
'Remediation': 'Check logs for more details',
|
|
254
|
+
'Service': self.service,
|
|
255
|
+
'CheckLogic': self.check_logic,
|
|
256
|
+
'CheckType': self.check_type
|
|
257
|
+
}
|
|
258
|
+
self.findings.append(finding)
|
|
259
|
+
|
|
260
|
+
return self.findings
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
from typing import Dict, List, Any
|
|
2
|
+
from sraverify.checks import SecurityCheck
|
|
3
|
+
from botocore.exceptions import ClientError
|
|
4
|
+
|
|
5
|
+
class SRAIAA4(SecurityCheck):
|
|
6
|
+
"""SRA-IAA-4: IAM Access Analyzer Organization Zone of Trust"""
|
|
7
|
+
|
|
8
|
+
def __init__(self, check_type="organization"):
|
|
9
|
+
super().__init__(check_type=check_type)
|
|
10
|
+
self.check_id = "SRA-IAA-4"
|
|
11
|
+
self.check_name = "IAM Access Analyzer Organization Zone of Trust"
|
|
12
|
+
self.severity = "HIGH"
|
|
13
|
+
self.description = ('This check verifies whether IAA external access analyzer is configured with a zone of trust '
|
|
14
|
+
'of your AWS organization. IAM Access Analyzer generates a finding for each instance of a '
|
|
15
|
+
'resource-based policy that grants access to a resource within your zone of trust to a principal '
|
|
16
|
+
'that is not within your zone of trust. When you configure an organization as the zone of trust '
|
|
17
|
+
'for an analyzer- IAA generates findings or each instance of a resource-based policy that grants '
|
|
18
|
+
'access to a resource within your AWS organization to a principal that is not within your AWS '
|
|
19
|
+
'organization.')
|
|
20
|
+
self.check_logic = ('1. Verify execution from Organization Management account | '
|
|
21
|
+
'2. List IAM Access Analyzers in current region | '
|
|
22
|
+
'3. Check for analyzer with type=ORGANIZATION and status=ACTIVE | '
|
|
23
|
+
'4. Verify IAM Analyzer configuration has Organization scope | '
|
|
24
|
+
'5. Check passes if organization-level analyzer is properly configured')
|
|
25
|
+
self.service = 'IAM'
|
|
26
|
+
|
|
27
|
+
def get_findings(self):
|
|
28
|
+
"""Return the findings"""
|
|
29
|
+
return self.findings
|
|
30
|
+
|
|
31
|
+
def run(self, session):
|
|
32
|
+
"""Run the security check"""
|
|
33
|
+
try:
|
|
34
|
+
region = session.region_name
|
|
35
|
+
account_id = session.client('sts').get_caller_identity()['Account']
|
|
36
|
+
|
|
37
|
+
# Initialize clients
|
|
38
|
+
org_client = session.client('organizations')
|
|
39
|
+
analyzer_client = session.client('accessanalyzer')
|
|
40
|
+
|
|
41
|
+
# Step 1: Verify we're in management account using org_mgmt_checker
|
|
42
|
+
is_management, error_message = self.org_checker.verify_org_management()
|
|
43
|
+
if not is_management:
|
|
44
|
+
finding = {
|
|
45
|
+
'CheckId': self.check_id,
|
|
46
|
+
'Status': 'ERROR',
|
|
47
|
+
'Region': region,
|
|
48
|
+
"Severity": self.severity,
|
|
49
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
50
|
+
'Description': self.description,
|
|
51
|
+
'ResourceId': account_id,
|
|
52
|
+
'ResourceType': 'AWS::Organizations::Account',
|
|
53
|
+
'AccountId': account_id,
|
|
54
|
+
'CheckedValue': 'Management Account Access',
|
|
55
|
+
'ActualValue': error_message if error_message else 'Not running from management account',
|
|
56
|
+
'Remediation': 'Run this check from the Organization Management Account',
|
|
57
|
+
'Service': self.service,
|
|
58
|
+
'CheckLogic': self.check_logic,
|
|
59
|
+
'CheckType': self.check_type
|
|
60
|
+
}
|
|
61
|
+
self.findings.append(finding)
|
|
62
|
+
return self.findings
|
|
63
|
+
|
|
64
|
+
# Steps 2 & 3: Check for organization-level analyzer
|
|
65
|
+
try:
|
|
66
|
+
analyzers = analyzer_client.list_analyzers()['analyzers']
|
|
67
|
+
org_analyzer = None
|
|
68
|
+
account_analyzers = []
|
|
69
|
+
|
|
70
|
+
for analyzer in analyzers:
|
|
71
|
+
if analyzer['type'] == 'ORGANIZATION' and analyzer['status'] == 'ACTIVE':
|
|
72
|
+
org_analyzer = analyzer
|
|
73
|
+
break
|
|
74
|
+
elif analyzer['type'] == 'ACCOUNT':
|
|
75
|
+
account_analyzers.append(analyzer['name'])
|
|
76
|
+
|
|
77
|
+
if not org_analyzer:
|
|
78
|
+
failure_details = []
|
|
79
|
+
if not analyzers:
|
|
80
|
+
failure_details.append("No analyzers found in the region")
|
|
81
|
+
if account_analyzers:
|
|
82
|
+
failure_details.append(f"Found account-level analyzers instead of organization-level: {', '.join(account_analyzers)}")
|
|
83
|
+
|
|
84
|
+
self.findings.append({
|
|
85
|
+
'CheckId': self.check_id,
|
|
86
|
+
'Status': 'FAIL',
|
|
87
|
+
'Region': region,
|
|
88
|
+
"Severity": self.severity,
|
|
89
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
90
|
+
'Description': self.description,
|
|
91
|
+
'ResourceId': account_id,
|
|
92
|
+
'ResourceType': 'AWS::AccessAnalyzer::Analyzer',
|
|
93
|
+
'AccountId': account_id,
|
|
94
|
+
'CheckedValue': 'Organization-level Access Analyzer',
|
|
95
|
+
'ActualValue': ' | '.join(failure_details) if failure_details else 'No active organization-level analyzer found',
|
|
96
|
+
'Remediation': 'Create an active organization-level IAM Access Analyzer in this region',
|
|
97
|
+
'Service': self.service,
|
|
98
|
+
'CheckLogic': self.check_logic,
|
|
99
|
+
'CheckType': self.check_type
|
|
100
|
+
})
|
|
101
|
+
return self.findings
|
|
102
|
+
|
|
103
|
+
# Step 4: Verify analyzer configuration
|
|
104
|
+
try:
|
|
105
|
+
analyzer_details = analyzer_client.get_analyzer(
|
|
106
|
+
analyzerName=org_analyzer['name']
|
|
107
|
+
)['analyzer']
|
|
108
|
+
|
|
109
|
+
# Verify organization scope
|
|
110
|
+
if analyzer_details.get('type') != 'ORGANIZATION':
|
|
111
|
+
self.findings.append({
|
|
112
|
+
'CheckId': self.check_id,
|
|
113
|
+
'Status': 'FAIL',
|
|
114
|
+
'Region': region,
|
|
115
|
+
"Severity": self.severity,
|
|
116
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
117
|
+
'Description': self.description,
|
|
118
|
+
'ResourceId': org_analyzer['arn'],
|
|
119
|
+
'ResourceType': 'AWS::AccessAnalyzer::Analyzer',
|
|
120
|
+
'AccountId': account_id,
|
|
121
|
+
'CheckedValue': 'Analyzer Organization Scope',
|
|
122
|
+
'ActualValue': f"Analyzer type is {analyzer_details.get('type')} instead of ORGANIZATION",
|
|
123
|
+
'Remediation': 'Reconfigure analyzer with organization scope or create new organization-level analyzer',
|
|
124
|
+
'Service': self.service,
|
|
125
|
+
'CheckLogic': self.check_logic,
|
|
126
|
+
'CheckType': self.check_type
|
|
127
|
+
})
|
|
128
|
+
else:
|
|
129
|
+
# Step 5: All checks passed
|
|
130
|
+
self.findings.append({
|
|
131
|
+
'CheckId': self.check_id,
|
|
132
|
+
'Status': 'PASS',
|
|
133
|
+
'Region': region,
|
|
134
|
+
"Severity": self.severity,
|
|
135
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
136
|
+
'Description': self.description,
|
|
137
|
+
'ResourceId': org_analyzer['arn'],
|
|
138
|
+
'ResourceType': 'AWS::AccessAnalyzer::Analyzer',
|
|
139
|
+
'AccountId': account_id,
|
|
140
|
+
'CheckedValue': 'Organization-level Access Analyzer Configuration',
|
|
141
|
+
'ActualValue': (f"Active organization analyzer: {org_analyzer['name']} | "
|
|
142
|
+
f"Type: {analyzer_details['type']} | "
|
|
143
|
+
f"Status: {analyzer_details['status']}"),
|
|
144
|
+
'Remediation': 'None required',
|
|
145
|
+
'Service': self.service,
|
|
146
|
+
'CheckLogic': self.check_logic,
|
|
147
|
+
'CheckType': self.check_type
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
except ClientError as e:
|
|
151
|
+
self.findings.append({
|
|
152
|
+
'CheckId': self.check_id,
|
|
153
|
+
'Status': 'ERROR',
|
|
154
|
+
'Region': region,
|
|
155
|
+
"Severity": self.severity,
|
|
156
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
157
|
+
'Description': self.description,
|
|
158
|
+
'ResourceId': org_analyzer['arn'],
|
|
159
|
+
'ResourceType': 'AWS::AccessAnalyzer::Analyzer',
|
|
160
|
+
'AccountId': account_id,
|
|
161
|
+
'CheckedValue': 'Analyzer Configuration',
|
|
162
|
+
'ActualValue': f'Error checking analyzer configuration: {str(e)}',
|
|
163
|
+
'Remediation': 'Verify IAM Access Analyzer permissions',
|
|
164
|
+
'Service': self.service,
|
|
165
|
+
'CheckLogic': self.check_logic,
|
|
166
|
+
'CheckType': self.check_type
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
except ClientError as e:
|
|
170
|
+
self.findings.append({
|
|
171
|
+
'CheckId': self.check_id,
|
|
172
|
+
'Status': 'ERROR',
|
|
173
|
+
'Region': region,
|
|
174
|
+
"Severity": self.severity,
|
|
175
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
176
|
+
'Description': self.description,
|
|
177
|
+
'ResourceId': account_id,
|
|
178
|
+
'ResourceType': 'AWS::AccessAnalyzer::Analyzer',
|
|
179
|
+
'AccountId': account_id,
|
|
180
|
+
'CheckedValue': 'Access Analyzer Access',
|
|
181
|
+
'ActualValue': f'Error accessing Access Analyzer: {str(e)}',
|
|
182
|
+
'Remediation': 'Verify IAM Access Analyzer permissions and service availability',
|
|
183
|
+
'Service': self.service,
|
|
184
|
+
'CheckLogic': self.check_logic,
|
|
185
|
+
'CheckType': self.check_type
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
except Exception as e:
|
|
189
|
+
self.findings.append({
|
|
190
|
+
'CheckId': self.check_id,
|
|
191
|
+
'Status': 'ERROR',
|
|
192
|
+
'Region': region,
|
|
193
|
+
"Severity": self.severity,
|
|
194
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
195
|
+
'Description': self.description,
|
|
196
|
+
'ResourceId': account_id,
|
|
197
|
+
'ResourceType': 'AWS::AccessAnalyzer::Analyzer',
|
|
198
|
+
'AccountId': account_id,
|
|
199
|
+
'CheckedValue': 'Check Execution',
|
|
200
|
+
'ActualValue': f'Error: {str(e)}',
|
|
201
|
+
'Remediation': 'Review error logs and verify AWS credentials and permissions',
|
|
202
|
+
'Service': self.service,
|
|
203
|
+
'CheckLogic': self.check_logic,
|
|
204
|
+
'CheckType': self.check_type
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
return self.findings
|