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,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Account client for interacting with AWS Account Management service.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Dict, Optional, Any
|
|
5
|
+
import boto3
|
|
6
|
+
from botocore.exceptions import ClientError
|
|
7
|
+
from sraverify.core.logging import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AccountClient:
|
|
11
|
+
"""Client for interacting with AWS Account Management service."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, region: str, session: Optional[boto3.Session] = None):
|
|
14
|
+
"""
|
|
15
|
+
Initialize Account 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('account', region_name=region)
|
|
24
|
+
|
|
25
|
+
def get_alternate_contact(self, contact_type: str, account_id: Optional[str] = None) -> Dict[str, Any]:
|
|
26
|
+
"""
|
|
27
|
+
Get alternate contact information for the specified type.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
contact_type: Type of contact (BILLING, OPERATIONS, or SECURITY)
|
|
31
|
+
account_id: Optional account ID (defaults to current account)
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Dictionary containing contact details or error information
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
params = {"AlternateContactType": contact_type}
|
|
38
|
+
if account_id:
|
|
39
|
+
params["AccountId"] = account_id
|
|
40
|
+
|
|
41
|
+
return self.client.get_alternate_contact(**params)
|
|
42
|
+
except ClientError as e:
|
|
43
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
44
|
+
error_message = str(e)
|
|
45
|
+
logger.debug(f"Error getting {contact_type} alternate contact in {self.region}: {error_message}")
|
|
46
|
+
return {
|
|
47
|
+
"Error": {
|
|
48
|
+
"Code": error_code,
|
|
49
|
+
"Message": error_message
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Audit Manager security checks.
|
|
3
|
+
"""
|
|
4
|
+
from sraverify.services.auditmanager.checks.sra_auditmanager_01 import SRA_AUDITMANAGER_01
|
|
5
|
+
from sraverify.services.auditmanager.checks.sra_auditmanager_02 import SRA_AUDITMANAGER_02
|
|
6
|
+
|
|
7
|
+
CHECKS = {
|
|
8
|
+
"SRA-AUDITMANAGER-01": SRA_AUDITMANAGER_01,
|
|
9
|
+
"SRA-AUDITMANAGER-02": SRA_AUDITMANAGER_02,
|
|
10
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base class for Audit Manager security checks.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Dict, Any
|
|
5
|
+
from sraverify.core.check import SecurityCheck
|
|
6
|
+
from sraverify.services.auditmanager.client import AuditManagerClient
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AuditManagerCheck(SecurityCheck):
|
|
10
|
+
"""Base class for all Audit Manager security checks."""
|
|
11
|
+
|
|
12
|
+
# Class-level cache shared across all instances
|
|
13
|
+
_account_status_cache = {}
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
"""Initialize Audit Manager base check."""
|
|
17
|
+
super().__init__(
|
|
18
|
+
account_type="application",
|
|
19
|
+
service="AuditManager",
|
|
20
|
+
resource_type="AWS::AuditManager::Account"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def _setup_clients(self):
|
|
24
|
+
"""Set up Audit Manager clients for each region."""
|
|
25
|
+
self._clients.clear()
|
|
26
|
+
if hasattr(self, 'regions') and self.regions:
|
|
27
|
+
for region in self.regions:
|
|
28
|
+
self._clients[region] = AuditManagerClient(region, session=self.session)
|
|
29
|
+
|
|
30
|
+
def get_account_status(self, region: str) -> Dict[str, Any]:
|
|
31
|
+
"""
|
|
32
|
+
Get account status for a specific region with caching.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
region: AWS region name
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Account status response or error information
|
|
39
|
+
"""
|
|
40
|
+
cache_key = f"{self.account_id}:{region}"
|
|
41
|
+
if cache_key in AuditManagerCheck._account_status_cache:
|
|
42
|
+
return AuditManagerCheck._account_status_cache[cache_key]
|
|
43
|
+
|
|
44
|
+
client = self.get_client(region)
|
|
45
|
+
if not client:
|
|
46
|
+
return {"Error": {"Code": "NoClient", "Message": f"No client available for region {region}"}}
|
|
47
|
+
|
|
48
|
+
status = client.get_account_status()
|
|
49
|
+
AuditManagerCheck._account_status_cache[cache_key] = status
|
|
50
|
+
return status
|
|
51
|
+
|
|
52
|
+
def get_organization_admin_account(self, region: str) -> Dict[str, Any]:
|
|
53
|
+
"""
|
|
54
|
+
Get organization admin account for a specific region with caching.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
region: AWS region name
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Organization admin account response or error information
|
|
61
|
+
"""
|
|
62
|
+
cache_key = f"org_admin:{region}"
|
|
63
|
+
if cache_key in AuditManagerCheck._account_status_cache:
|
|
64
|
+
return AuditManagerCheck._account_status_cache[cache_key]
|
|
65
|
+
|
|
66
|
+
client = self.get_client(region)
|
|
67
|
+
if not client:
|
|
68
|
+
return {"Error": {"Code": "NoClient", "Message": f"No client available for region {region}"}}
|
|
69
|
+
|
|
70
|
+
admin_info = client.get_organization_admin_account()
|
|
71
|
+
AuditManagerCheck._account_status_cache[cache_key] = admin_info
|
|
72
|
+
return admin_info
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Audit Manager checks
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Check if AWS Audit Manager is enabled.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Dict, List, Any
|
|
5
|
+
from sraverify.services.auditmanager.base import AuditManagerCheck
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SRA_AUDITMANAGER_01(AuditManagerCheck):
|
|
9
|
+
"""Check if AWS Audit Manager is enabled."""
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
"""Initialize Audit Manager enabled check."""
|
|
13
|
+
super().__init__()
|
|
14
|
+
self.check_id = "SRA-AUDITMANAGER-01"
|
|
15
|
+
self.check_name = "AWS Audit Manager is enabled"
|
|
16
|
+
self.description = "This check verifies that AWS Audit Manager is enabled in the AWS account. Audit Manager helps you continuously audit your AWS usage to simplify how you assess risk and compliance with regulations and industry standards."
|
|
17
|
+
self.severity = "MEDIUM"
|
|
18
|
+
self.check_logic = "Check account registration status using GetAccountStatus API. Check passes if status is ACTIVE."
|
|
19
|
+
|
|
20
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
21
|
+
"""
|
|
22
|
+
Execute the check.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
List of findings
|
|
26
|
+
"""
|
|
27
|
+
for region in self.regions:
|
|
28
|
+
status_response = self.get_account_status(region)
|
|
29
|
+
|
|
30
|
+
if "Error" in status_response:
|
|
31
|
+
self.findings.append(self.create_finding(
|
|
32
|
+
status="ERROR",
|
|
33
|
+
region=region,
|
|
34
|
+
resource_id=None,
|
|
35
|
+
actual_value=status_response["Error"].get("Message", "Unknown error"),
|
|
36
|
+
remediation="Check IAM permissions for Audit Manager API access"
|
|
37
|
+
))
|
|
38
|
+
else:
|
|
39
|
+
account_status = status_response.get("status", "UNKNOWN")
|
|
40
|
+
|
|
41
|
+
if account_status == "ACTIVE":
|
|
42
|
+
self.findings.append(self.create_finding(
|
|
43
|
+
status="PASS",
|
|
44
|
+
region=region,
|
|
45
|
+
resource_id=f"auditmanager:{self.account_id}",
|
|
46
|
+
actual_value=account_status,
|
|
47
|
+
remediation=""
|
|
48
|
+
))
|
|
49
|
+
else:
|
|
50
|
+
self.findings.append(self.create_finding(
|
|
51
|
+
status="FAIL",
|
|
52
|
+
region=region,
|
|
53
|
+
resource_id=None,
|
|
54
|
+
actual_value=account_status,
|
|
55
|
+
remediation=f"Enable AWS Audit Manager in {region} by registering the account using the RegisterAccount API or AWS console"
|
|
56
|
+
))
|
|
57
|
+
|
|
58
|
+
return self.findings
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Check if Audit Manager delegated admin is the audit account.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Dict, List, Any
|
|
5
|
+
from sraverify.services.auditmanager.base import AuditManagerCheck
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SRA_AUDITMANAGER_02(AuditManagerCheck):
|
|
9
|
+
"""Check if Audit Manager delegated admin is the audit account."""
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
"""Initialize Audit Manager delegated admin check."""
|
|
13
|
+
super().__init__()
|
|
14
|
+
self.account_type = "management"
|
|
15
|
+
self.check_id = "SRA-AUDITMANAGER-02"
|
|
16
|
+
self.check_name = "Audit Manager delegated admin is the audit account"
|
|
17
|
+
self.description = "This check verifies that the AWS Audit Manager delegated administrator is configured as the audit account. The delegated administrator should be the security tooling account to centralize audit management."
|
|
18
|
+
self.severity = "HIGH"
|
|
19
|
+
self.check_logic = "Get organization admin account using GetOrganizationAdminAccount API and verify it matches the audit account ID."
|
|
20
|
+
|
|
21
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
22
|
+
"""
|
|
23
|
+
Execute the check.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
List of findings
|
|
27
|
+
"""
|
|
28
|
+
for region in self.regions:
|
|
29
|
+
admin_response = self.get_organization_admin_account(region)
|
|
30
|
+
|
|
31
|
+
if "Error" in admin_response:
|
|
32
|
+
error_code = admin_response["Error"].get("Code", "")
|
|
33
|
+
error_message = admin_response["Error"].get("Message", "")
|
|
34
|
+
|
|
35
|
+
if error_code == "ResourceNotFoundException":
|
|
36
|
+
self.findings.append(self.create_finding(
|
|
37
|
+
status="FAIL",
|
|
38
|
+
region=region,
|
|
39
|
+
resource_id=None,
|
|
40
|
+
actual_value="No delegated administrator configured",
|
|
41
|
+
remediation=f"Configure a delegated administrator for Audit Manager in {region} using RegisterOrganizationAdminAccount API"
|
|
42
|
+
))
|
|
43
|
+
elif "Please complete AWS Audit Manager setup" in error_message:
|
|
44
|
+
self.findings.append(self.create_finding(
|
|
45
|
+
status="FAIL",
|
|
46
|
+
region=region,
|
|
47
|
+
resource_id=None,
|
|
48
|
+
actual_value="Audit Manager setup not completed in this account",
|
|
49
|
+
remediation=f"Complete AWS Audit Manager setup from the home page in {region} before configuring delegated administrator"
|
|
50
|
+
))
|
|
51
|
+
else:
|
|
52
|
+
self.findings.append(self.create_finding(
|
|
53
|
+
status="ERROR",
|
|
54
|
+
region=region,
|
|
55
|
+
resource_id=None,
|
|
56
|
+
actual_value=error_message,
|
|
57
|
+
remediation="Check IAM permissions for Audit Manager API access"
|
|
58
|
+
))
|
|
59
|
+
else:
|
|
60
|
+
admin_account_id = admin_response.get("adminAccountId")
|
|
61
|
+
audit_accounts = getattr(self, '_audit_accounts', [])
|
|
62
|
+
|
|
63
|
+
if admin_account_id in audit_accounts:
|
|
64
|
+
self.findings.append(self.create_finding(
|
|
65
|
+
status="PASS",
|
|
66
|
+
region=region,
|
|
67
|
+
resource_id=f"auditmanager:admin:{admin_account_id}",
|
|
68
|
+
actual_value=admin_account_id,
|
|
69
|
+
remediation=""
|
|
70
|
+
))
|
|
71
|
+
else:
|
|
72
|
+
self.findings.append(self.create_finding(
|
|
73
|
+
status="FAIL",
|
|
74
|
+
region=region,
|
|
75
|
+
resource_id=f"auditmanager:admin:{admin_account_id}",
|
|
76
|
+
actual_value=admin_account_id,
|
|
77
|
+
remediation=f"Change delegated administrator to audit account in {region}. Current admin: {admin_account_id}, Expected audit accounts: {audit_accounts}"
|
|
78
|
+
))
|
|
79
|
+
|
|
80
|
+
return self.findings
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Audit Manager client for interacting with AWS Audit Manager service.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Dict, Optional, Any
|
|
5
|
+
import boto3
|
|
6
|
+
from botocore.exceptions import ClientError
|
|
7
|
+
from sraverify.core.logging import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AuditManagerClient:
|
|
11
|
+
"""Client for interacting with AWS Audit Manager service."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, region: str, session: Optional[boto3.Session] = None):
|
|
14
|
+
"""
|
|
15
|
+
Initialize Audit Manager 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('auditmanager', region_name=region)
|
|
24
|
+
|
|
25
|
+
def get_account_status(self) -> Dict[str, Any]:
|
|
26
|
+
"""
|
|
27
|
+
Get the registration status of the account in Audit Manager.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Dictionary containing status or error information
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
response = self.client.get_account_status()
|
|
34
|
+
return response
|
|
35
|
+
except ClientError as e:
|
|
36
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
37
|
+
error_message = str(e)
|
|
38
|
+
logger.error(f"Error getting account status in {self.region}: {error_message}")
|
|
39
|
+
|
|
40
|
+
def get_organization_admin_account(self) -> Dict[str, Any]:
|
|
41
|
+
"""
|
|
42
|
+
Get the delegated administrator account for the organization.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
Dictionary containing admin account info or error information
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
response = self.client.get_organization_admin_account()
|
|
49
|
+
return response
|
|
50
|
+
except ClientError as e:
|
|
51
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
52
|
+
error_message = str(e)
|
|
53
|
+
|
|
54
|
+
# Don't log setup required errors as they're handled as FAIL in the check
|
|
55
|
+
if "Please complete AWS Audit Manager setup" not in error_message:
|
|
56
|
+
logger.error(f"Error getting organization admin account in {self.region}: {error_message}")
|
|
57
|
+
|
|
58
|
+
return {"Error": {"Code": error_code, "Message": error_message}}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cloudtrail security checks.
|
|
3
|
+
"""
|
|
4
|
+
from sraverify.services.cloudtrail.checks.sra_cloudtrail_01 import SRA_CLOUDTRAIL_01
|
|
5
|
+
from sraverify.services.cloudtrail.checks.sra_cloudtrail_02 import SRA_CLOUDTRAIL_02
|
|
6
|
+
from sraverify.services.cloudtrail.checks.sra_cloudtrail_03 import SRA_CLOUDTRAIL_03
|
|
7
|
+
from sraverify.services.cloudtrail.checks.sra_cloudtrail_04 import SRA_CLOUDTRAIL_04
|
|
8
|
+
from sraverify.services.cloudtrail.checks.sra_cloudtrail_05 import SRA_CLOUDTRAIL_05
|
|
9
|
+
from sraverify.services.cloudtrail.checks.sra_cloudtrail_06 import SRA_CLOUDTRAIL_06
|
|
10
|
+
from sraverify.services.cloudtrail.checks.sra_cloudtrail_07 import SRA_CLOUDTRAIL_07
|
|
11
|
+
from sraverify.services.cloudtrail.checks.sra_cloudtrail_08 import SRA_CLOUDTRAIL_08
|
|
12
|
+
from sraverify.services.cloudtrail.checks.sra_cloudtrail_09 import SRA_CLOUDTRAIL_09
|
|
13
|
+
from sraverify.services.cloudtrail.checks.sra_cloudtrail_10 import SRA_CLOUDTRAIL_10
|
|
14
|
+
from sraverify.services.cloudtrail.checks.sra_cloudtrail_11 import SRA_CLOUDTRAIL_11
|
|
15
|
+
from sraverify.services.cloudtrail.checks.sra_cloudtrail_12 import SRA_CLOUDTRAIL_12
|
|
16
|
+
from sraverify.services.cloudtrail.checks.sra_cloudtrail_13 import SRA_CLOUDTRAIL_13
|
|
17
|
+
|
|
18
|
+
# Register checks
|
|
19
|
+
CHECKS = {
|
|
20
|
+
"SRA-CLOUDTRAIL-01": SRA_CLOUDTRAIL_01,
|
|
21
|
+
"SRA-CLOUDTRAIL-02": SRA_CLOUDTRAIL_02,
|
|
22
|
+
"SRA-CLOUDTRAIL-03": SRA_CLOUDTRAIL_03,
|
|
23
|
+
"SRA-CLOUDTRAIL-04": SRA_CLOUDTRAIL_04,
|
|
24
|
+
"SRA-CLOUDTRAIL-05": SRA_CLOUDTRAIL_05,
|
|
25
|
+
"SRA-CLOUDTRAIL-06": SRA_CLOUDTRAIL_06,
|
|
26
|
+
"SRA-CLOUDTRAIL-07": SRA_CLOUDTRAIL_07,
|
|
27
|
+
"SRA-CLOUDTRAIL-08": SRA_CLOUDTRAIL_08,
|
|
28
|
+
"SRA-CLOUDTRAIL-09": SRA_CLOUDTRAIL_09,
|
|
29
|
+
"SRA-CLOUDTRAIL-10": SRA_CLOUDTRAIL_10,
|
|
30
|
+
"SRA-CLOUDTRAIL-11": SRA_CLOUDTRAIL_11,
|
|
31
|
+
"SRA-CLOUDTRAIL-12": SRA_CLOUDTRAIL_12,
|
|
32
|
+
"SRA-CLOUDTRAIL-13": SRA_CLOUDTRAIL_13,
|
|
33
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base class for CloudTrail security checks.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Optional, Dict, Any
|
|
5
|
+
from sraverify.core.check import SecurityCheck
|
|
6
|
+
from sraverify.services.cloudtrail.client import CloudTrailClient
|
|
7
|
+
from sraverify.core.logging import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CloudTrailCheck(SecurityCheck):
|
|
11
|
+
"""Base class for all CloudTrail security checks."""
|
|
12
|
+
|
|
13
|
+
# Class-level caches shared across all instances - only keeping the ones specified
|
|
14
|
+
_describe_trails_cache = {}
|
|
15
|
+
_trail_status_cache = {}
|
|
16
|
+
_delegated_admin_account_id_cache = {}
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
"""Initialize CloudTrail base check."""
|
|
20
|
+
super().__init__(
|
|
21
|
+
account_type="management",
|
|
22
|
+
service="CloudTrail",
|
|
23
|
+
resource_type="AWS::CloudTrail::Trail"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def _setup_clients(self):
|
|
27
|
+
"""Set up CloudTrail clients for each region."""
|
|
28
|
+
# Clear existing clients
|
|
29
|
+
self._clients.clear()
|
|
30
|
+
# Set up new clients only if regions are initialized
|
|
31
|
+
if hasattr(self, 'regions') and self.regions:
|
|
32
|
+
for region in self.regions:
|
|
33
|
+
self._clients[region] = CloudTrailClient(region, session=self.session)
|
|
34
|
+
|
|
35
|
+
def get_client(self, region: str) -> Optional[CloudTrailClient]:
|
|
36
|
+
"""
|
|
37
|
+
Get CloudTrail client for a specific region.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
region: AWS region name
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
CloudTrailClient for the region or None if not available
|
|
44
|
+
"""
|
|
45
|
+
return self._clients.get(region)
|
|
46
|
+
|
|
47
|
+
def describe_trails(self, include_shadow_trails: bool = True) -> List[Dict[str, Any]]:
|
|
48
|
+
"""
|
|
49
|
+
Get all CloudTrail trails across all regions using the client with caching.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
include_shadow_trails: Include shadow trails in the response
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
List of all trails
|
|
56
|
+
"""
|
|
57
|
+
if not self.regions:
|
|
58
|
+
logger.warning("No regions specified")
|
|
59
|
+
return []
|
|
60
|
+
|
|
61
|
+
# Use session region name as part of cache key
|
|
62
|
+
cache_key = f"describe_trails:{self.session.region_name}:{include_shadow_trails}"
|
|
63
|
+
if cache_key in self.__class__._describe_trails_cache:
|
|
64
|
+
logger.debug(f"Using cached trails for {cache_key}")
|
|
65
|
+
return self.__class__._describe_trails_cache[cache_key]
|
|
66
|
+
|
|
67
|
+
# Use any region to get all trails
|
|
68
|
+
client = self.get_client(self.regions[0])
|
|
69
|
+
if not client:
|
|
70
|
+
logger.warning("No CloudTrail client available")
|
|
71
|
+
return []
|
|
72
|
+
|
|
73
|
+
# Get all trails using the client
|
|
74
|
+
trails = client.describe_trails(include_shadow_trails=include_shadow_trails)
|
|
75
|
+
|
|
76
|
+
# Cache the results
|
|
77
|
+
self.__class__._describe_trails_cache[cache_key] = trails
|
|
78
|
+
logger.debug(f"Cached {len(trails)} trails for {cache_key}")
|
|
79
|
+
|
|
80
|
+
return trails
|
|
81
|
+
|
|
82
|
+
def get_organization_trails(self) -> List[Dict[str, Any]]:
|
|
83
|
+
"""
|
|
84
|
+
Get all organization CloudTrail trails.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
List of organization trails
|
|
88
|
+
"""
|
|
89
|
+
# Get all trails first
|
|
90
|
+
all_trails = self.describe_trails()
|
|
91
|
+
|
|
92
|
+
# Filter for organization trails
|
|
93
|
+
org_trails = [
|
|
94
|
+
trail for trail in all_trails
|
|
95
|
+
if trail.get('IsOrganizationTrail', False)
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
logger.debug(f"Found {len(org_trails)} organization trails")
|
|
99
|
+
return org_trails
|
|
100
|
+
|
|
101
|
+
def get_trail_status(self, region: str, trail_arn: str) -> Dict[str, Any]:
|
|
102
|
+
"""
|
|
103
|
+
Get status of a specific CloudTrail trail using the client with caching.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
region: AWS region name
|
|
107
|
+
trail_arn: ARN of the trail
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Dictionary containing trail status
|
|
111
|
+
"""
|
|
112
|
+
# Check cache first
|
|
113
|
+
cache_key = f"{trail_arn}:{region}"
|
|
114
|
+
if cache_key in self.__class__._trail_status_cache:
|
|
115
|
+
logger.debug(f"Using cached trail status for {trail_arn} in {region}")
|
|
116
|
+
return self.__class__._trail_status_cache[cache_key]
|
|
117
|
+
|
|
118
|
+
client = self.get_client(region)
|
|
119
|
+
if not client:
|
|
120
|
+
logger.warning(f"No CloudTrail client available for region {region}")
|
|
121
|
+
return {}
|
|
122
|
+
|
|
123
|
+
# Get trail status from client
|
|
124
|
+
status = client.get_trail_status(trail_arn)
|
|
125
|
+
|
|
126
|
+
# Cache the result
|
|
127
|
+
self.__class__._trail_status_cache[cache_key] = status
|
|
128
|
+
logger.debug(f"Cached trail status for {trail_arn} in {region}")
|
|
129
|
+
|
|
130
|
+
return status
|
|
131
|
+
|
|
132
|
+
def get_delegated_administrators(self) -> List[Dict[str, Any]]:
|
|
133
|
+
"""
|
|
134
|
+
Get CloudTrail delegated administrators with caching.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
List of delegated administrators
|
|
138
|
+
"""
|
|
139
|
+
if not self.regions:
|
|
140
|
+
logger.warning("No regions specified")
|
|
141
|
+
return []
|
|
142
|
+
|
|
143
|
+
account_id = self.account_id
|
|
144
|
+
if not account_id:
|
|
145
|
+
logger.warning("Could not determine account ID")
|
|
146
|
+
return []
|
|
147
|
+
|
|
148
|
+
# Check cache first
|
|
149
|
+
cache_key = f"{account_id}:{self.session.region_name}"
|
|
150
|
+
if cache_key in self.__class__._delegated_admin_account_id_cache:
|
|
151
|
+
logger.debug(f"Using cached delegated administrators for {cache_key}")
|
|
152
|
+
return self.__class__._delegated_admin_account_id_cache[cache_key]
|
|
153
|
+
|
|
154
|
+
# Use any region to get delegated administrators
|
|
155
|
+
client = self.get_client(self.regions[0])
|
|
156
|
+
if not client:
|
|
157
|
+
logger.warning("No CloudTrail client available")
|
|
158
|
+
return []
|
|
159
|
+
|
|
160
|
+
# Get delegated administrators from client
|
|
161
|
+
delegated_admins = client.list_delegated_administrators()
|
|
162
|
+
|
|
163
|
+
# Cache the results
|
|
164
|
+
self.__class__._delegated_admin_account_id_cache[cache_key] = delegated_admins
|
|
165
|
+
logger.debug(f"Cached {len(delegated_admins)} delegated administrators for {cache_key}")
|
|
166
|
+
|
|
167
|
+
return delegated_admins
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""CloudTrail security checks."""
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SRA-CLOUDTRAIL-01: Organization CloudTrail Configuration.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from sraverify.services.cloudtrail.base import CloudTrailCheck
|
|
6
|
+
from sraverify.core.logging import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SRA_CLOUDTRAIL_01(CloudTrailCheck):
|
|
10
|
+
"""Check if an Organization trail is configured for the AWS Organization."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
"""Initialize the check."""
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.check_id = "SRA-CLOUDTRAIL-01"
|
|
16
|
+
self.check_name = "An Organization trail is configured for the AWS Organization"
|
|
17
|
+
self.account_type = "management"
|
|
18
|
+
self.severity = "HIGH"
|
|
19
|
+
self.description = (
|
|
20
|
+
"This check verifies that an organization trail is configured for your AWS Organization. "
|
|
21
|
+
"It is important to have uniform logging strategy for your AWS environment. Organization trail "
|
|
22
|
+
"logs all events for all AWS accounts in that organization and delivers logs to a single S3 bucket, "
|
|
23
|
+
"CloudWatch Logs and Event Bridge. Organization trails are automatically applied to all member accounts "
|
|
24
|
+
"in the organization. Member accounts can see the organization trail, but can't modify or delete it. "
|
|
25
|
+
"Organization trail should be configured for all AWS regions even if you are not operating out of any region."
|
|
26
|
+
)
|
|
27
|
+
self.check_logic = (
|
|
28
|
+
"Check if at least one trail has IsOrganizationTrail set to true."
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
32
|
+
"""
|
|
33
|
+
Execute the check.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
List of findings
|
|
37
|
+
"""
|
|
38
|
+
findings = []
|
|
39
|
+
|
|
40
|
+
# Get all trails using the base class method
|
|
41
|
+
# This will use the cache if available or make API calls if needed
|
|
42
|
+
all_trails = self.describe_trails()
|
|
43
|
+
|
|
44
|
+
# Filter for organization trails
|
|
45
|
+
org_trails = [
|
|
46
|
+
trail for trail in all_trails
|
|
47
|
+
if trail.get('IsOrganizationTrail', False)
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
if not org_trails:
|
|
51
|
+
findings.append(
|
|
52
|
+
self.create_finding(
|
|
53
|
+
status="FAIL",
|
|
54
|
+
region="global",
|
|
55
|
+
resource_id=f"organization/{self.account_id}",
|
|
56
|
+
checked_value="IsOrganizationTrail: true",
|
|
57
|
+
actual_value="No organization trails found",
|
|
58
|
+
remediation=(
|
|
59
|
+
"Create an organization trail in the management account using the AWS CLI command: "
|
|
60
|
+
f"aws cloudtrail create-trail --name org-trail --is-organization-trail --s3-bucket-name cloudtrail-logs-{self.account_id} "
|
|
61
|
+
f"--is-multi-region-trail --region {self.regions[0] if self.regions else 'us-east-1'}"
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
return findings
|
|
66
|
+
|
|
67
|
+
# If we have organization trails, create a PASS finding for each one
|
|
68
|
+
for trail in org_trails:
|
|
69
|
+
trail_name = trail.get('Name', 'Unknown')
|
|
70
|
+
trail_arn = trail.get('TrailARN', 'Unknown')
|
|
71
|
+
|
|
72
|
+
findings.append(
|
|
73
|
+
self.create_finding(
|
|
74
|
+
status="PASS",
|
|
75
|
+
region="global",
|
|
76
|
+
resource_id=trail_arn,
|
|
77
|
+
checked_value="IsOrganizationTrail: true",
|
|
78
|
+
actual_value=f"Organization trail '{trail_name}' is configured",
|
|
79
|
+
remediation="No remediation needed"
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return findings
|