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,242 @@
|
|
|
1
|
+
from typing import Dict, List, Any
|
|
2
|
+
from sraverify.checks import SecurityCheck
|
|
3
|
+
from botocore.exceptions import ClientError
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
class SRACT11(SecurityCheck):
|
|
7
|
+
"""SRA-CT-11: Organization Trail Log Archive Configuration"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, check_type="organization"):
|
|
10
|
+
"""Initialize the check with organization type"""
|
|
11
|
+
super().__init__(check_type=check_type)
|
|
12
|
+
self.check_id = "SRA-CT-11"
|
|
13
|
+
self.check_name = "Organization trail Logs are delivered to a centralized S3 bucket in the Log Archive Account"
|
|
14
|
+
self.description = ('This check verifies whether the corresponding S3 buckets that stores organization trail logs in created in Log Archive account. '
|
|
15
|
+
'This separates the management and usage of CloudTrail log privileges. The Log Archive account is dedicated to '
|
|
16
|
+
'ingesting and archiving all security-related logs and backups.')
|
|
17
|
+
self.service = "CloudTrail"
|
|
18
|
+
self.severity = "HIGH"
|
|
19
|
+
self.check_type = check_type
|
|
20
|
+
self.check_logic = ('1. Verify execution from Organization Management Account | '
|
|
21
|
+
'2. Identify Log Archive account in the organization | '
|
|
22
|
+
'3. List CloudTrail trails and identify organization trails | '
|
|
23
|
+
'4. Verify S3 bucket is in Log Archive account | '
|
|
24
|
+
'5. Check passes if organization trail logs are delivered to Log Archive account S3 bucket')
|
|
25
|
+
self.logger = logging.getLogger(self.__class__.__name__)
|
|
26
|
+
self.findings = []
|
|
27
|
+
|
|
28
|
+
def get_findings(self) -> List[Dict[str, Any]]:
|
|
29
|
+
"""Return the findings"""
|
|
30
|
+
return self.findings
|
|
31
|
+
|
|
32
|
+
def find_log_archive_account(self, organizations_client) -> str:
|
|
33
|
+
"""Find the Log Archive account ID in the organization"""
|
|
34
|
+
try:
|
|
35
|
+
paginator = organizations_client.get_paginator('list_accounts')
|
|
36
|
+
for page in paginator.paginate():
|
|
37
|
+
for account in page['Accounts']:
|
|
38
|
+
# Check account name for "log" pattern (case insensitive)
|
|
39
|
+
if 'log' in account.get('Name', '').lower():
|
|
40
|
+
self.logger.debug(f"Found Log Archive account: {account['Name']} ({account['Id']})")
|
|
41
|
+
return account['Id']
|
|
42
|
+
|
|
43
|
+
# Check account tags as backup method
|
|
44
|
+
try:
|
|
45
|
+
tags = organizations_client.list_tags_for_resource(ResourceId=account['Id'])
|
|
46
|
+
for tag in tags.get('Tags', []):
|
|
47
|
+
if (tag.get('Key') == 'aws-control-tower' and
|
|
48
|
+
'log' in tag.get('Value', '').lower()):
|
|
49
|
+
self.logger.debug(f"Found Log Archive account by tag: {account['Name']} ({account['Id']})")
|
|
50
|
+
return account['Id']
|
|
51
|
+
except ClientError:
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
self.logger.error("No account found with 'log' in the name or tags")
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
except ClientError as e:
|
|
58
|
+
self.logger.error(f"Error listing organization accounts: {str(e)}")
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
def run(self, session) -> None:
|
|
62
|
+
"""Run the security check"""
|
|
63
|
+
try:
|
|
64
|
+
# Get account information
|
|
65
|
+
sts_client = session.client('sts')
|
|
66
|
+
account_id = sts_client.get_caller_identity()['Account']
|
|
67
|
+
region = session.region_name
|
|
68
|
+
self.logger.debug(f"Running check for account: {account_id} in region: {region}")
|
|
69
|
+
|
|
70
|
+
# Step 1: Verify we're in management account using org_mgmt_checker
|
|
71
|
+
is_management, error_message = self.org_checker.verify_org_management()
|
|
72
|
+
if not is_management:
|
|
73
|
+
finding = {
|
|
74
|
+
'CheckId': self.check_id,
|
|
75
|
+
'Status': 'ERROR',
|
|
76
|
+
'Region': region,
|
|
77
|
+
"Severity": self.severity,
|
|
78
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
79
|
+
'Description': self.description,
|
|
80
|
+
'ResourceId': account_id,
|
|
81
|
+
'ResourceType': 'AWS::Organizations::Account',
|
|
82
|
+
'AccountId': account_id,
|
|
83
|
+
'CheckedValue': 'Management Account Access',
|
|
84
|
+
'ActualValue': error_message if error_message else 'Not running from management account',
|
|
85
|
+
'Remediation': 'Run this check from the Organization Management Account',
|
|
86
|
+
'Service': self.service,
|
|
87
|
+
'CheckLogic': self.check_logic,
|
|
88
|
+
'CheckType': self.check_type
|
|
89
|
+
}
|
|
90
|
+
self.findings.append(finding)
|
|
91
|
+
return self.findings
|
|
92
|
+
|
|
93
|
+
# Initialize clients
|
|
94
|
+
organizations_client = session.client('organizations')
|
|
95
|
+
cloudtrail_client = session.client('cloudtrail')
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
# Find Log Archive account
|
|
99
|
+
log_archive_account = self.find_log_archive_account(organizations_client)
|
|
100
|
+
if not log_archive_account:
|
|
101
|
+
self.findings.append({
|
|
102
|
+
"CheckId": self.check_id,
|
|
103
|
+
"Status": "ERROR",
|
|
104
|
+
"Region": region,
|
|
105
|
+
"Severity": self.severity,
|
|
106
|
+
"Title": f"{self.check_id} {self.check_name}",
|
|
107
|
+
"Description": self.description,
|
|
108
|
+
"ResourceId": "log-archive-account",
|
|
109
|
+
"ResourceType": "AWS::Organizations::Account",
|
|
110
|
+
"AccountId": account_id,
|
|
111
|
+
"CheckedValue": "Log Archive Account Identification",
|
|
112
|
+
"ActualValue": "Could not identify Log Archive account",
|
|
113
|
+
"Remediation": "Verify Control Tower setup and Log Archive account existence",
|
|
114
|
+
"Service": self.service,
|
|
115
|
+
"CheckLogic": self.check_logic,
|
|
116
|
+
"CheckType": self.check_type
|
|
117
|
+
})
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
# List trails and find organization trails
|
|
121
|
+
trails = cloudtrail_client.describe_trails(includeShadowTrails=True)
|
|
122
|
+
org_trails = [t for t in trails['trailList'] if t.get('IsOrganizationTrail')]
|
|
123
|
+
self.logger.debug(f"Found {len(org_trails)} organization trails")
|
|
124
|
+
|
|
125
|
+
if not org_trails:
|
|
126
|
+
self.findings.append({
|
|
127
|
+
"CheckId": self.check_id,
|
|
128
|
+
"Status": "FAIL",
|
|
129
|
+
"Region": region,
|
|
130
|
+
"Severity": self.severity,
|
|
131
|
+
"Title": f"{self.check_id} {self.check_name}",
|
|
132
|
+
"Description": self.description,
|
|
133
|
+
"ResourceId": "organization-trail",
|
|
134
|
+
"ResourceType": "AWS::CloudTrail::Trail",
|
|
135
|
+
"AccountId": account_id,
|
|
136
|
+
"CheckedValue": "Organization Trail Configuration",
|
|
137
|
+
"ActualValue": "No organization trail found",
|
|
138
|
+
"Remediation": "Create an organization trail with S3 bucket in Log Archive account",
|
|
139
|
+
"Service": self.service,
|
|
140
|
+
"CheckLogic": self.check_logic,
|
|
141
|
+
"CheckType": self.check_type
|
|
142
|
+
})
|
|
143
|
+
return
|
|
144
|
+
|
|
145
|
+
# Check each organization trail for S3 bucket location
|
|
146
|
+
valid_trail = None
|
|
147
|
+
for trail in org_trails:
|
|
148
|
+
s3_bucket = trail.get('S3BucketName')
|
|
149
|
+
if not s3_bucket:
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
# Check if the bucket name contains the Log Archive account ID
|
|
153
|
+
if f"aws-controltower-logs-{log_archive_account}" in s3_bucket:
|
|
154
|
+
valid_trail = trail
|
|
155
|
+
self.logger.debug(f"Found trail with S3 bucket in Log Archive account: {trail['Name']}")
|
|
156
|
+
break
|
|
157
|
+
|
|
158
|
+
# Create finding based on S3 bucket location
|
|
159
|
+
if valid_trail:
|
|
160
|
+
self.findings.append({
|
|
161
|
+
"CheckId": self.check_id,
|
|
162
|
+
"Status": "PASS",
|
|
163
|
+
"Region": region,
|
|
164
|
+
"Severity": self.severity,
|
|
165
|
+
"Title": f"{self.check_id} {self.check_name}",
|
|
166
|
+
"Description": self.description,
|
|
167
|
+
"ResourceId": valid_trail['TrailARN'],
|
|
168
|
+
"ResourceType": "AWS::CloudTrail::Trail",
|
|
169
|
+
"AccountId": account_id,
|
|
170
|
+
"CheckedValue": "S3 Bucket Location",
|
|
171
|
+
"ActualValue": f"Organization trail {valid_trail['Name']} delivers logs to S3 bucket {valid_trail['S3BucketName']} in Log Archive account ({log_archive_account})",
|
|
172
|
+
"Remediation": "None required",
|
|
173
|
+
"Service": self.service,
|
|
174
|
+
"CheckLogic": self.check_logic,
|
|
175
|
+
"CheckType": self.check_type
|
|
176
|
+
})
|
|
177
|
+
else:
|
|
178
|
+
actual_value = f"Trail S3 bucket is not in Log Archive account ({log_archive_account})"
|
|
179
|
+
remediation = "Configure organization trail to deliver logs to S3 bucket in Log Archive account"
|
|
180
|
+
if org_trails:
|
|
181
|
+
trail = org_trails[0]
|
|
182
|
+
if not trail.get('S3BucketName'):
|
|
183
|
+
actual_value = f"Trail {trail['Name']} has no S3 bucket configured"
|
|
184
|
+
remediation = "Configure S3 bucket in Log Archive account for the organization trail"
|
|
185
|
+
|
|
186
|
+
self.findings.append({
|
|
187
|
+
"CheckId": self.check_id,
|
|
188
|
+
"Status": "FAIL",
|
|
189
|
+
"Region": region,
|
|
190
|
+
"Severity": self.severity,
|
|
191
|
+
"Title": f"{self.check_id} {self.check_name}",
|
|
192
|
+
"Description": self.description,
|
|
193
|
+
"ResourceId": (valid_trail or org_trails[0])['TrailARN'],
|
|
194
|
+
"ResourceType": "AWS::CloudTrail::Trail",
|
|
195
|
+
"AccountId": account_id,
|
|
196
|
+
"CheckedValue": "S3 Bucket Location",
|
|
197
|
+
"ActualValue": actual_value,
|
|
198
|
+
"Remediation": remediation,
|
|
199
|
+
"Service": self.service,
|
|
200
|
+
"CheckLogic": self.check_logic,
|
|
201
|
+
"CheckType": self.check_type
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
except ClientError as e:
|
|
205
|
+
self.logger.error(f"Error accessing AWS services: {str(e)}")
|
|
206
|
+
self.findings.append({
|
|
207
|
+
"CheckId": self.check_id,
|
|
208
|
+
"Status": "ERROR",
|
|
209
|
+
"Region": region,
|
|
210
|
+
"Severity": self.severity,
|
|
211
|
+
"Title": f"{self.check_id} {self.check_name}",
|
|
212
|
+
"Description": self.description,
|
|
213
|
+
"ResourceId": "aws-services",
|
|
214
|
+
"ResourceType": "AWS::CloudTrail::Trail",
|
|
215
|
+
"AccountId": account_id,
|
|
216
|
+
"CheckedValue": "AWS Services Access",
|
|
217
|
+
"ActualValue": f"Error accessing AWS services: {str(e)}",
|
|
218
|
+
"Remediation": "Verify required permissions",
|
|
219
|
+
"Service": self.service,
|
|
220
|
+
"CheckLogic": self.check_logic,
|
|
221
|
+
"CheckType": self.check_type
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
except Exception as e:
|
|
225
|
+
self.logger.error(f"Unexpected error in check: {str(e)}")
|
|
226
|
+
self.findings.append({
|
|
227
|
+
"CheckId": self.check_id,
|
|
228
|
+
"Status": "ERROR",
|
|
229
|
+
"Region": region if 'region' in locals() else session.region_name,
|
|
230
|
+
"Severity": self.severity,
|
|
231
|
+
"Title": f"{self.check_id} {self.check_name}",
|
|
232
|
+
"Description": self.description,
|
|
233
|
+
"ResourceId": "check-execution",
|
|
234
|
+
"ResourceType": "AWS::CloudTrail::Trail",
|
|
235
|
+
"AccountId": account_id if 'account_id' in locals() else "unknown",
|
|
236
|
+
"CheckedValue": "Check Execution",
|
|
237
|
+
"ActualValue": f"Unexpected error: {str(e)}",
|
|
238
|
+
"Remediation": "Contact support team",
|
|
239
|
+
"Service": self.service,
|
|
240
|
+
"CheckLogic": self.check_logic,
|
|
241
|
+
"CheckType": self.check_type
|
|
242
|
+
})
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
from typing import Dict, List, Any
|
|
2
|
+
from sraverify.checks import SecurityCheck
|
|
3
|
+
from botocore.exceptions import ClientError
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
class SRACT12(SecurityCheck):
|
|
7
|
+
"""SRA-CT-12: CloudTrail Delegated Administrator Configuration"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, check_type="organization"):
|
|
10
|
+
"""Initialize the check with organization type"""
|
|
11
|
+
super().__init__(check_type=check_type)
|
|
12
|
+
self.check_id = "SRA-CT-12"
|
|
13
|
+
self.check_name = "Delegated Administrator set for CloudTrail"
|
|
14
|
+
self.description = ('This check verifies whether CloudTrail service administration for the AWS Organization '
|
|
15
|
+
'is delegated out to AWS Organization management account. The delegated administrator has '
|
|
16
|
+
'permissions to create and manage analyzers with the AWS organization as the zone of trust.')
|
|
17
|
+
self.service = "CloudTrail"
|
|
18
|
+
self.severity = "MEDIUM"
|
|
19
|
+
self.check_type = check_type
|
|
20
|
+
self.check_logic = ('1. Verify execution from Organization Management Account | '
|
|
21
|
+
'2. Get management account ID from Organizations | '
|
|
22
|
+
'3. List CloudTrail delegated administrators | '
|
|
23
|
+
'4. Check passes if at least one delegated administrator is configured')
|
|
24
|
+
self.logger = logging.getLogger(self.__class__.__name__)
|
|
25
|
+
self.findings = []
|
|
26
|
+
|
|
27
|
+
def get_findings(self) -> List[Dict[str, Any]]:
|
|
28
|
+
"""Return the findings"""
|
|
29
|
+
return self.findings
|
|
30
|
+
|
|
31
|
+
def run(self, session) -> None:
|
|
32
|
+
"""Run the security check"""
|
|
33
|
+
try:
|
|
34
|
+
# Get account information
|
|
35
|
+
sts_client = session.client('sts')
|
|
36
|
+
account_id = sts_client.get_caller_identity()['Account']
|
|
37
|
+
region = session.region_name
|
|
38
|
+
self.logger.debug(f"Running check for account: {account_id} in region: {region}")
|
|
39
|
+
|
|
40
|
+
# Step 1: Verify execution from Organization Management Account
|
|
41
|
+
is_management, error_message = self.org_checker.verify_org_management()
|
|
42
|
+
if not is_management:
|
|
43
|
+
self.findings.append({
|
|
44
|
+
'CheckId': self.check_id,
|
|
45
|
+
'Status': 'ERROR',
|
|
46
|
+
'Region': region,
|
|
47
|
+
"Severity": self.severity,
|
|
48
|
+
'Title': f"{self.check_id} {self.check_name}",
|
|
49
|
+
'Description': self.description,
|
|
50
|
+
'ResourceId': account_id,
|
|
51
|
+
'ResourceType': 'AWS::Organizations::Account',
|
|
52
|
+
'AccountId': account_id,
|
|
53
|
+
'CheckedValue': 'Management Account Access',
|
|
54
|
+
'ActualValue': error_message if error_message else 'Not running from management account',
|
|
55
|
+
'Remediation': 'Run this check from the Organization Management Account',
|
|
56
|
+
'Service': self.service,
|
|
57
|
+
'CheckLogic': self.check_logic,
|
|
58
|
+
'CheckType': self.check_type
|
|
59
|
+
})
|
|
60
|
+
return self.findings
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
# Step 2: Get management account ID from Organizations
|
|
64
|
+
organizations_client = session.client('organizations')
|
|
65
|
+
|
|
66
|
+
# Step 3: List CloudTrail delegated administrators
|
|
67
|
+
try:
|
|
68
|
+
delegated_admins = organizations_client.list_delegated_administrators(
|
|
69
|
+
ServicePrincipal='cloudtrail.amazonaws.com'
|
|
70
|
+
)
|
|
71
|
+
admin_accounts = delegated_admins.get('DelegatedAdministrators', [])
|
|
72
|
+
|
|
73
|
+
# Step 4: Check if at least one delegated administrator is configured
|
|
74
|
+
if admin_accounts:
|
|
75
|
+
# Format account list for actual value
|
|
76
|
+
admin_list = ', '.join([f"{admin['Id']}" for admin in admin_accounts])
|
|
77
|
+
status = "PASS"
|
|
78
|
+
actual_value = f"CloudTrail delegated administrators configured: {admin_list}"
|
|
79
|
+
remediation = "None required"
|
|
80
|
+
else:
|
|
81
|
+
status = "FAIL"
|
|
82
|
+
actual_value = "No CloudTrail delegated administrators configured"
|
|
83
|
+
remediation = "Configure at least one delegated administrator for CloudTrail using the CloudTrail console or AWS CLI"
|
|
84
|
+
|
|
85
|
+
self.findings.append({
|
|
86
|
+
"CheckId": self.check_id,
|
|
87
|
+
"Status": status,
|
|
88
|
+
"Region": region,
|
|
89
|
+
"Severity": self.severity,
|
|
90
|
+
"Title": f"{self.check_id} {self.check_name}",
|
|
91
|
+
"Description": self.description,
|
|
92
|
+
"ResourceId": "cloudtrail-delegated-admin",
|
|
93
|
+
"ResourceType": "AWS::Organizations::Account",
|
|
94
|
+
"AccountId": account_id,
|
|
95
|
+
"CheckedValue": "Delegated Administrator Configuration",
|
|
96
|
+
"ActualValue": actual_value,
|
|
97
|
+
"Remediation": remediation,
|
|
98
|
+
"Service": self.service,
|
|
99
|
+
"CheckLogic": self.check_logic,
|
|
100
|
+
"CheckType": self.check_type
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
except ClientError as e:
|
|
104
|
+
if 'AccessDeniedException' in str(e):
|
|
105
|
+
self.findings.append({
|
|
106
|
+
"CheckId": self.check_id,
|
|
107
|
+
"Status": "ERROR",
|
|
108
|
+
"Region": region,
|
|
109
|
+
"Severity": self.severity,
|
|
110
|
+
"Title": f"{self.check_id} {self.check_name}",
|
|
111
|
+
"Description": self.description,
|
|
112
|
+
"ResourceId": "organizations-permissions",
|
|
113
|
+
"ResourceType": "AWS::Organizations::Account",
|
|
114
|
+
"AccountId": account_id,
|
|
115
|
+
"CheckedValue": "Organizations Permissions",
|
|
116
|
+
"ActualValue": "Insufficient permissions to list delegated administrators",
|
|
117
|
+
"Remediation": "Verify Organizations permissions in management account",
|
|
118
|
+
"Service": self.service,
|
|
119
|
+
"CheckLogic": self.check_logic,
|
|
120
|
+
"CheckType": self.check_type
|
|
121
|
+
})
|
|
122
|
+
else:
|
|
123
|
+
raise e
|
|
124
|
+
|
|
125
|
+
except ClientError as e:
|
|
126
|
+
self.logger.error(f"Error accessing Organizations: {str(e)}")
|
|
127
|
+
self.findings.append({
|
|
128
|
+
"CheckId": self.check_id,
|
|
129
|
+
"Status": "ERROR",
|
|
130
|
+
"Region": region,
|
|
131
|
+
"Severity": self.severity,
|
|
132
|
+
"Title": f"{self.check_id} {self.check_name}",
|
|
133
|
+
"Description": self.description,
|
|
134
|
+
"ResourceId": "organizations",
|
|
135
|
+
"ResourceType": "AWS::Organizations::Account",
|
|
136
|
+
"AccountId": account_id,
|
|
137
|
+
"CheckedValue": "Organizations API Access",
|
|
138
|
+
"ActualValue": f"Error accessing Organizations: {str(e)}",
|
|
139
|
+
"Remediation": "Verify Organizations permissions",
|
|
140
|
+
"Service": self.service,
|
|
141
|
+
"CheckLogic": self.check_logic,
|
|
142
|
+
"CheckType": self.check_type
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
except Exception as e:
|
|
146
|
+
self.logger.error(f"Unexpected error in check: {str(e)}")
|
|
147
|
+
self.findings.append({
|
|
148
|
+
"CheckId": self.check_id,
|
|
149
|
+
"Status": "ERROR",
|
|
150
|
+
"Region": region if 'region' in locals() else session.region_name,
|
|
151
|
+
"Severity": self.severity,
|
|
152
|
+
"Title": f"{self.check_id} {self.check_name}",
|
|
153
|
+
"Description": self.description,
|
|
154
|
+
"ResourceId": "check-execution",
|
|
155
|
+
"ResourceType": "AWS::CloudTrail::Trail",
|
|
156
|
+
"AccountId": account_id if 'account_id' in locals() else "unknown",
|
|
157
|
+
"CheckedValue": "Check Execution",
|
|
158
|
+
"ActualValue": f"Unexpected error: {str(e)}",
|
|
159
|
+
"Remediation": "Contact support team",
|
|
160
|
+
"Service": self.service,
|
|
161
|
+
"CheckLogic": self.check_logic,
|
|
162
|
+
"CheckType": self.check_type
|
|
163
|
+
})
|