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,461 @@
|
|
|
1
|
+
"""Security Lake service module."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Dict, Any, Optional
|
|
4
|
+
from sraverify.core.check import SecurityCheck
|
|
5
|
+
from sraverify.services.securitylake.client import SecurityLakeClient
|
|
6
|
+
from sraverify.core.logging import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SecurityLakeCheck(SecurityCheck):
|
|
10
|
+
"""Security Lake service class with integrated check functionality."""
|
|
11
|
+
|
|
12
|
+
# Class-level caches shared across all instances
|
|
13
|
+
_subscribers_cache = {}
|
|
14
|
+
_security_lake_status_cache = {}
|
|
15
|
+
_organization_configuration_cache = {}
|
|
16
|
+
_delegated_admin_cache = {}
|
|
17
|
+
_organization_accounts_cache = {}
|
|
18
|
+
_log_sources_cache = {}
|
|
19
|
+
_sqs_encryption_cache = {}
|
|
20
|
+
|
|
21
|
+
def __init__(self):
|
|
22
|
+
"""Initialize Security Lake service."""
|
|
23
|
+
super().__init__(
|
|
24
|
+
account_type="log-archive",
|
|
25
|
+
service="SecurityLake",
|
|
26
|
+
resource_type="AWS::SecurityLake::SecurityLake"
|
|
27
|
+
)
|
|
28
|
+
# Initialize log archive account attribute
|
|
29
|
+
self._log_archive_accounts = None
|
|
30
|
+
|
|
31
|
+
def _setup_clients(self):
|
|
32
|
+
"""Set up Security Lake clients for each region."""
|
|
33
|
+
# Clear existing clients
|
|
34
|
+
self._clients.clear()
|
|
35
|
+
# Set up new clients only if regions are initialized
|
|
36
|
+
if hasattr(self, 'regions') and self.regions:
|
|
37
|
+
for region in self.regions:
|
|
38
|
+
self._clients[region] = SecurityLakeClient(region, session=self.session)
|
|
39
|
+
|
|
40
|
+
def get_client(self, region: str) -> Optional[SecurityLakeClient]:
|
|
41
|
+
"""
|
|
42
|
+
Get Security Lake client for a specific region.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
region: AWS region name
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
SecurityLakeClient for the region or None if not available
|
|
49
|
+
"""
|
|
50
|
+
client = self._clients.get(region)
|
|
51
|
+
if not client:
|
|
52
|
+
logger.debug(f"No Security Lake client available for region {region}")
|
|
53
|
+
return client
|
|
54
|
+
|
|
55
|
+
def get_subscribers(self, region: str) -> List[Dict[str, Any]]:
|
|
56
|
+
"""
|
|
57
|
+
Get Security Lake subscribers with caching.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
region: AWS region name
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
List of subscribers
|
|
64
|
+
"""
|
|
65
|
+
if not self.account_id:
|
|
66
|
+
logger.debug("Could not determine account ID")
|
|
67
|
+
return []
|
|
68
|
+
|
|
69
|
+
# Check cache first
|
|
70
|
+
cache_key = f"{self.account_id}:{region}"
|
|
71
|
+
if cache_key in self.__class__._subscribers_cache:
|
|
72
|
+
logger.debug(f"Using cached subscribers for {cache_key}")
|
|
73
|
+
return self.__class__._subscribers_cache[cache_key]
|
|
74
|
+
|
|
75
|
+
client = self.get_client(region)
|
|
76
|
+
if not client:
|
|
77
|
+
return []
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
# Get subscribers from client
|
|
81
|
+
subscribers = client.list_subscribers()
|
|
82
|
+
|
|
83
|
+
# Cache the results
|
|
84
|
+
self.__class__._subscribers_cache[cache_key] = subscribers
|
|
85
|
+
logger.debug(f"Cached {len(subscribers)} subscribers for {cache_key}")
|
|
86
|
+
|
|
87
|
+
return subscribers
|
|
88
|
+
except Exception as e:
|
|
89
|
+
logger.debug(f"Error getting subscribers in {region}: {e}")
|
|
90
|
+
return []
|
|
91
|
+
|
|
92
|
+
def is_security_lake_enabled(self, region: str) -> bool:
|
|
93
|
+
"""
|
|
94
|
+
Check if Security Lake is enabled with caching.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
region: AWS region name
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
True if Security Lake is enabled, False otherwise
|
|
101
|
+
"""
|
|
102
|
+
if not self.account_id:
|
|
103
|
+
logger.debug("Could not determine account ID")
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
# Check cache first
|
|
107
|
+
cache_key = f"{self.account_id}:{region}"
|
|
108
|
+
if cache_key in self.__class__._security_lake_status_cache:
|
|
109
|
+
logger.debug(f"Using cached Security Lake status for {cache_key}")
|
|
110
|
+
return self.__class__._security_lake_status_cache[cache_key]
|
|
111
|
+
|
|
112
|
+
client = self.get_client(region)
|
|
113
|
+
if not client:
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
# Check if Security Lake is enabled
|
|
118
|
+
is_enabled = client.is_security_lake_enabled()
|
|
119
|
+
|
|
120
|
+
# Cache the results
|
|
121
|
+
self.__class__._security_lake_status_cache[cache_key] = is_enabled
|
|
122
|
+
logger.debug(f"Cached Security Lake status for {cache_key}: {is_enabled}")
|
|
123
|
+
|
|
124
|
+
return is_enabled
|
|
125
|
+
except Exception as e:
|
|
126
|
+
logger.debug(f"Error checking Security Lake status in {region}: {e}")
|
|
127
|
+
self.__class__._security_lake_status_cache[cache_key] = False
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
def get_organization_configuration(self, region: str) -> Dict[str, Any]:
|
|
131
|
+
"""
|
|
132
|
+
Get Security Lake organization configuration with caching.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
region: AWS region name
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Organization configuration
|
|
139
|
+
"""
|
|
140
|
+
if not self.account_id:
|
|
141
|
+
logger.debug("Could not determine account ID")
|
|
142
|
+
return {}
|
|
143
|
+
|
|
144
|
+
# Check cache first
|
|
145
|
+
cache_key = f"{self.account_id}:{region}"
|
|
146
|
+
if cache_key in self.__class__._organization_configuration_cache:
|
|
147
|
+
logger.debug(f"Using cached organization configuration for {cache_key}")
|
|
148
|
+
return self.__class__._organization_configuration_cache[cache_key]
|
|
149
|
+
|
|
150
|
+
client = self.get_client(region)
|
|
151
|
+
if not client:
|
|
152
|
+
return {}
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
# Get organization configuration from client
|
|
156
|
+
org_config = client.get_organization_configuration()
|
|
157
|
+
|
|
158
|
+
# Cache the results
|
|
159
|
+
self.__class__._organization_configuration_cache[cache_key] = org_config
|
|
160
|
+
logger.debug(f"Cached organization configuration for {cache_key}")
|
|
161
|
+
|
|
162
|
+
return org_config
|
|
163
|
+
except Exception as e:
|
|
164
|
+
logger.debug(f"Error getting organization configuration in {region}: {e}")
|
|
165
|
+
self.__class__._organization_configuration_cache[cache_key] = {}
|
|
166
|
+
return {}
|
|
167
|
+
|
|
168
|
+
def get_log_source_status(self, region: str, source_name: str) -> bool:
|
|
169
|
+
"""
|
|
170
|
+
Check if a specific log source is enabled in a region.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
region: AWS region name
|
|
174
|
+
source_name: Name of the log source to check (e.g., 'ROUTE53', 'VPC_FLOW')
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
True if the log source is enabled, False otherwise
|
|
178
|
+
"""
|
|
179
|
+
if not self.account_id:
|
|
180
|
+
logger.debug("Could not determine account ID")
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
# Check cache first
|
|
184
|
+
cache_key = f"{self.account_id}:{region}"
|
|
185
|
+
if cache_key not in self.__class__._log_sources_cache:
|
|
186
|
+
client = self.get_client(region)
|
|
187
|
+
if not client:
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
# Get log sources from client and cache them
|
|
191
|
+
log_sources = client.list_log_sources()
|
|
192
|
+
self.__class__._log_sources_cache[cache_key] = log_sources
|
|
193
|
+
logger.debug(f"Cached {len(log_sources)} log source entries for {cache_key}")
|
|
194
|
+
else:
|
|
195
|
+
log_sources = self.__class__._log_sources_cache[cache_key]
|
|
196
|
+
logger.debug(f"Using cached log sources for {cache_key}")
|
|
197
|
+
|
|
198
|
+
# Navigate the nested structure to find the source
|
|
199
|
+
# Structure: sources[].sources[].awsLogSource.sourceName
|
|
200
|
+
for log_source_entry in log_sources:
|
|
201
|
+
for source in log_source_entry.get("sources", []):
|
|
202
|
+
aws_log_source = source.get("awsLogSource", {})
|
|
203
|
+
if aws_log_source.get("sourceName") == source_name:
|
|
204
|
+
# Check if source is collecting
|
|
205
|
+
source_status = source.get("sourceStatus", [])
|
|
206
|
+
for status in source_status:
|
|
207
|
+
if status.get("status") == "COLLECTING":
|
|
208
|
+
return True
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
return False
|
|
212
|
+
|
|
213
|
+
def get_delegated_administrators(self, region: str) -> List[Dict[str, Any]]:
|
|
214
|
+
"""
|
|
215
|
+
Get Security Lake delegated administrators with caching.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
region: AWS region name
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
List of delegated administrators
|
|
222
|
+
"""
|
|
223
|
+
if not self.account_id:
|
|
224
|
+
logger.debug("Could not determine account ID")
|
|
225
|
+
return []
|
|
226
|
+
|
|
227
|
+
# Check cache first
|
|
228
|
+
cache_key = f"{self.account_id}:{region}"
|
|
229
|
+
if cache_key in self.__class__._delegated_admin_cache:
|
|
230
|
+
logger.debug(f"Using cached delegated administrators for {cache_key}")
|
|
231
|
+
return self.__class__._delegated_admin_cache[cache_key]
|
|
232
|
+
|
|
233
|
+
client = self.get_client(region)
|
|
234
|
+
if not client:
|
|
235
|
+
return []
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
# Get delegated administrators from client
|
|
239
|
+
delegated_admins = client.list_delegated_administrators()
|
|
240
|
+
|
|
241
|
+
# Cache the results
|
|
242
|
+
self.__class__._delegated_admin_cache[cache_key] = delegated_admins
|
|
243
|
+
logger.debug(f"Cached {len(delegated_admins)} delegated administrators for {cache_key}")
|
|
244
|
+
|
|
245
|
+
return delegated_admins
|
|
246
|
+
except Exception as e:
|
|
247
|
+
logger.debug(f"Error getting delegated administrators in {region}: {e}")
|
|
248
|
+
return []
|
|
249
|
+
|
|
250
|
+
def get_organization_accounts(self, region: str) -> List[Dict[str, Any]]:
|
|
251
|
+
"""
|
|
252
|
+
Get all organization accounts with caching.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
region: AWS region name
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
List of organization accounts
|
|
259
|
+
"""
|
|
260
|
+
if not self.account_id:
|
|
261
|
+
logger.debug("Could not determine account ID")
|
|
262
|
+
return []
|
|
263
|
+
|
|
264
|
+
# Check cache first
|
|
265
|
+
cache_key = f"{self.account_id}:{region}"
|
|
266
|
+
if cache_key in self.__class__._organization_accounts_cache:
|
|
267
|
+
logger.debug(f"Using cached organization accounts for {cache_key}")
|
|
268
|
+
return self.__class__._organization_accounts_cache[cache_key]
|
|
269
|
+
|
|
270
|
+
client = self.get_client(region)
|
|
271
|
+
if not client:
|
|
272
|
+
return []
|
|
273
|
+
|
|
274
|
+
try:
|
|
275
|
+
# Get organization accounts from client
|
|
276
|
+
accounts = client.list_organization_accounts()
|
|
277
|
+
|
|
278
|
+
# Cache the results
|
|
279
|
+
self.__class__._organization_accounts_cache[cache_key] = accounts
|
|
280
|
+
logger.debug(f"Cached {len(accounts)} organization accounts for {cache_key}")
|
|
281
|
+
|
|
282
|
+
return accounts
|
|
283
|
+
except Exception as e:
|
|
284
|
+
logger.debug(f"Error getting organization accounts in {region}: {e}")
|
|
285
|
+
return []
|
|
286
|
+
|
|
287
|
+
def get_sqs_queue_encryption(self, region: str, queue_url: str) -> Optional[str]:
|
|
288
|
+
"""
|
|
289
|
+
Get SQS queue encryption key with caching.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
region: AWS region name
|
|
293
|
+
queue_url: SQS queue URL
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
KMS key ID or None if not encrypted/error
|
|
297
|
+
"""
|
|
298
|
+
cache_key = f"{self.account_id}:{region}:{queue_url}"
|
|
299
|
+
if cache_key in self.__class__._sqs_encryption_cache:
|
|
300
|
+
logger.debug(f"Using cached SQS encryption for {queue_url}")
|
|
301
|
+
return self.__class__._sqs_encryption_cache[cache_key]
|
|
302
|
+
|
|
303
|
+
client = self.get_client(region)
|
|
304
|
+
if not client:
|
|
305
|
+
logger.debug(f"No client available for region {region}")
|
|
306
|
+
self.__class__._sqs_encryption_cache[cache_key] = None
|
|
307
|
+
return None
|
|
308
|
+
|
|
309
|
+
try:
|
|
310
|
+
kms_key = client.get_sqs_queue_encryption(queue_url)
|
|
311
|
+
self.__class__._sqs_encryption_cache[cache_key] = kms_key
|
|
312
|
+
logger.debug(f"SQS queue {queue_url} encryption: {kms_key}")
|
|
313
|
+
return kms_key
|
|
314
|
+
except Exception as e:
|
|
315
|
+
logger.error(f"Error getting SQS encryption for {queue_url} in {region}: {e}")
|
|
316
|
+
self.__class__._sqs_encryption_cache[cache_key] = None
|
|
317
|
+
return None
|
|
318
|
+
|
|
319
|
+
def get_data_lake_sources(self, region: str, account_id: str = None) -> List[Dict[str, Any]]:
|
|
320
|
+
"""
|
|
321
|
+
Get Security Lake data lake sources with caching.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
region: AWS region name
|
|
325
|
+
account_id: Optional account ID. If None, gets all accounts.
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
List of data lake sources
|
|
329
|
+
"""
|
|
330
|
+
cache_key = f"{self.account_id}:{region}:data_lake_sources:{account_id or 'all'}"
|
|
331
|
+
if cache_key in self.__class__._log_sources_cache:
|
|
332
|
+
logger.debug(f"Using cached data lake sources for {cache_key}")
|
|
333
|
+
return self.__class__._log_sources_cache[cache_key]
|
|
334
|
+
|
|
335
|
+
client = self.get_client(region)
|
|
336
|
+
if not client:
|
|
337
|
+
logger.debug(f"No client available for region {region}")
|
|
338
|
+
self.__class__._log_sources_cache[cache_key] = []
|
|
339
|
+
return []
|
|
340
|
+
|
|
341
|
+
try:
|
|
342
|
+
# Call with or without account_id based on parameter
|
|
343
|
+
data_lake_sources = client.get_data_lake_sources(account_id)
|
|
344
|
+
self.__class__._log_sources_cache[cache_key] = data_lake_sources
|
|
345
|
+
logger.debug(f"Cached {len(data_lake_sources)} data lake sources for {cache_key}")
|
|
346
|
+
return data_lake_sources
|
|
347
|
+
except Exception as e:
|
|
348
|
+
# Use debug level for UnauthorizedException as it's expected when Security Lake isn't enabled
|
|
349
|
+
if "UnauthorizedException" in str(e) or "Unauthorized" in str(e):
|
|
350
|
+
logger.debug(f"Security Lake not enabled in {region}: {e}")
|
|
351
|
+
else:
|
|
352
|
+
logger.error(f"Error getting data lake sources in {region}: {e}")
|
|
353
|
+
self.__class__._log_sources_cache[cache_key] = []
|
|
354
|
+
return []
|
|
355
|
+
|
|
356
|
+
def get_enabled_regions(self) -> List[str]:
|
|
357
|
+
"""
|
|
358
|
+
Get list of regions where Security Lake is enabled.
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
List of region names where Security Lake is enabled
|
|
362
|
+
"""
|
|
363
|
+
enabled_regions = []
|
|
364
|
+
|
|
365
|
+
for region in self.regions:
|
|
366
|
+
if self.is_security_lake_enabled(region):
|
|
367
|
+
logger.debug(f"Security Lake is enabled in {region}")
|
|
368
|
+
enabled_regions.append(region)
|
|
369
|
+
else:
|
|
370
|
+
logger.debug(f"Security Lake is not enabled in {region}")
|
|
371
|
+
|
|
372
|
+
return enabled_regions
|
|
373
|
+
|
|
374
|
+
def get_account_log_source_status(self, region: str, source_name: str) -> bool:
|
|
375
|
+
"""
|
|
376
|
+
Check if a specific log source is enabled for the current account in a region.
|
|
377
|
+
Uses get_data_lake_sources API for account-specific status.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
region: AWS region name
|
|
381
|
+
source_name: Name of the log source to check (e.g., 'ROUTE53', 'VPC_FLOW')
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
True if the log source is enabled for this account, False otherwise
|
|
385
|
+
"""
|
|
386
|
+
if not self.account_id:
|
|
387
|
+
logger.debug("Could not determine account ID")
|
|
388
|
+
return False
|
|
389
|
+
|
|
390
|
+
# Check cache first
|
|
391
|
+
cache_key = f"account_sources:{self.account_id}:{region}"
|
|
392
|
+
if cache_key not in self.__class__._log_sources_cache:
|
|
393
|
+
client = self.get_client(region)
|
|
394
|
+
if not client:
|
|
395
|
+
return False
|
|
396
|
+
|
|
397
|
+
# Get account-specific data lake sources and cache them
|
|
398
|
+
# Pass the account ID as a string (not the full account object)
|
|
399
|
+
data_lake_sources = client.get_data_lake_sources(self.account_id)
|
|
400
|
+
self.__class__._log_sources_cache[cache_key] = data_lake_sources
|
|
401
|
+
logger.debug(f"Cached {len(data_lake_sources)} account data lake sources for {cache_key}")
|
|
402
|
+
else:
|
|
403
|
+
data_lake_sources = self.__class__._log_sources_cache[cache_key]
|
|
404
|
+
logger.debug(f"Using cached account data lake sources for {cache_key}")
|
|
405
|
+
|
|
406
|
+
# Check if the source is enabled for this account
|
|
407
|
+
for source_entry in data_lake_sources:
|
|
408
|
+
if source_entry.get("account") == self.account_id and source_entry.get("sourceName") == source_name:
|
|
409
|
+
return True
|
|
410
|
+
|
|
411
|
+
return False
|
|
412
|
+
|
|
413
|
+
def check_log_source_configured(self, region: str, source_name: str, account_id: str = None,
|
|
414
|
+
required_version: str = "2.0") -> bool:
|
|
415
|
+
"""
|
|
416
|
+
Check if a log source is configured using list-log-sources API.
|
|
417
|
+
This checks configuration, not collection status.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
region: AWS region name
|
|
421
|
+
source_name: Name of the log source (e.g., 'ROUTE53', 'VPC_FLOW')
|
|
422
|
+
account_id: Account ID to check (defaults to current account)
|
|
423
|
+
required_version: Required source version (default: "2.0")
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
True if source is configured with correct version, False otherwise
|
|
427
|
+
"""
|
|
428
|
+
target_account = account_id or self.account_id
|
|
429
|
+
if not target_account:
|
|
430
|
+
logger.debug("Could not determine account ID")
|
|
431
|
+
return False
|
|
432
|
+
|
|
433
|
+
# Check cache first
|
|
434
|
+
cache_key = f"list_sources:{target_account}:{region}"
|
|
435
|
+
if cache_key not in self.__class__._log_sources_cache:
|
|
436
|
+
client = self.get_client(region)
|
|
437
|
+
if not client:
|
|
438
|
+
return False
|
|
439
|
+
|
|
440
|
+
# Get configured log sources and cache them
|
|
441
|
+
try:
|
|
442
|
+
log_sources = client.list_log_sources(regions=[region], accounts=[target_account])
|
|
443
|
+
self.__class__._log_sources_cache[cache_key] = log_sources
|
|
444
|
+
logger.debug(f"Cached log sources for {cache_key}")
|
|
445
|
+
except Exception as e:
|
|
446
|
+
logger.error(f"Error listing log sources: {e}")
|
|
447
|
+
return False
|
|
448
|
+
else:
|
|
449
|
+
log_sources = self.__class__._log_sources_cache[cache_key]
|
|
450
|
+
logger.debug(f"Using cached log sources for {cache_key}")
|
|
451
|
+
|
|
452
|
+
# Check if the source is configured with correct version
|
|
453
|
+
for source_entry in log_sources:
|
|
454
|
+
if source_entry.get("account") == target_account and source_entry.get("region") == region:
|
|
455
|
+
for source in source_entry.get("sources", []):
|
|
456
|
+
aws_log_source = source.get("awsLogSource", {})
|
|
457
|
+
if (aws_log_source.get("sourceName") == source_name and
|
|
458
|
+
aws_log_source.get("sourceVersion") == required_version):
|
|
459
|
+
return True
|
|
460
|
+
|
|
461
|
+
return False
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Security Lake checks."""
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Check if Amazon Security Lake is enabled."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Dict, Any
|
|
4
|
+
from sraverify.services.securitylake.base import SecurityLakeCheck
|
|
5
|
+
from sraverify.core.logging import logger
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SRA_SECURITYLAKE_01(SecurityLakeCheck):
|
|
9
|
+
"""Check if Amazon Security Lake is enabled."""
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
"""Initialize check."""
|
|
13
|
+
super().__init__()
|
|
14
|
+
self.account_type = "log-archive" # Check all org accounts from delegated admin
|
|
15
|
+
self.check_id = "SRA-SECURITYLAKE-01"
|
|
16
|
+
self.check_name = "Security Lake is enabled for all organization accounts"
|
|
17
|
+
self.severity = "HIGH"
|
|
18
|
+
self.description = (
|
|
19
|
+
"This check verifies whether Amazon Security Lake is enabled for all active accounts in the organization. "
|
|
20
|
+
"Amazon Security Lake is a fully managed security data lake service that you "
|
|
21
|
+
"can use to automatically centralize security data from AWS environments, "
|
|
22
|
+
"SaaS providers, on premises, cloud sources, and third-party sources into a "
|
|
23
|
+
"purpose-built data lake that's stored in your AWS account. The data lake is "
|
|
24
|
+
"backed by Amazon S3 buckets, and you retain ownership over your data. You "
|
|
25
|
+
"must enable security lake in every AWS account and AWS region to collect "
|
|
26
|
+
"security logs and event from your entire AWS environment. "
|
|
27
|
+
"This check runs from the delegated administrator account "
|
|
28
|
+
"and validates configuration across all organization member accounts."
|
|
29
|
+
)
|
|
30
|
+
self.check_logic = (
|
|
31
|
+
"Checks if Security Lake is enabled for all active organization accounts by calling get_data_lake_sources API. "
|
|
32
|
+
"The check passes if Security Lake is enabled for all active accounts in the organization. "
|
|
33
|
+
"The check fails if any active account does not have Security Lake enabled."
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
37
|
+
"""
|
|
38
|
+
Execute the check.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
List of findings
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
for region in self.regions:
|
|
45
|
+
logger.debug(f"Checking if Security Lake is enabled for all organization accounts in {region}")
|
|
46
|
+
|
|
47
|
+
# Get all organization accounts
|
|
48
|
+
org_accounts = self.get_organization_accounts(region)
|
|
49
|
+
if not org_accounts:
|
|
50
|
+
logger.debug("No organization accounts found, checking current account only")
|
|
51
|
+
org_accounts = [{'Id': self.account_id, 'Status': 'ACTIVE'}]
|
|
52
|
+
|
|
53
|
+
# Create sets of active account IDs
|
|
54
|
+
active_org_account_ids = set()
|
|
55
|
+
for account in org_accounts:
|
|
56
|
+
if account.get('Status') == 'ACTIVE':
|
|
57
|
+
active_org_account_ids.add(account.get('Id'))
|
|
58
|
+
|
|
59
|
+
# Get accounts with Security Lake enabled
|
|
60
|
+
enabled_accounts = set()
|
|
61
|
+
sources_data = self.get_data_lake_sources(region) # No account_id = get all accounts
|
|
62
|
+
for source in sources_data:
|
|
63
|
+
account_id = source.get('account')
|
|
64
|
+
if account_id:
|
|
65
|
+
enabled_accounts.add(account_id)
|
|
66
|
+
|
|
67
|
+
# Check each account in the organization
|
|
68
|
+
for account_id in active_org_account_ids:
|
|
69
|
+
resource_id = f"arn:aws:securitylake:{region}:{account_id}:datalake/default"
|
|
70
|
+
|
|
71
|
+
if account_id not in enabled_accounts:
|
|
72
|
+
self.findings.append(
|
|
73
|
+
self.create_finding(
|
|
74
|
+
status="FAIL",
|
|
75
|
+
region=region,
|
|
76
|
+
resource_id=resource_id,
|
|
77
|
+
checked_value="Security Lake enabled",
|
|
78
|
+
actual_value=f"Security Lake is not enabled for account {account_id}",
|
|
79
|
+
remediation=(
|
|
80
|
+
f"Enable Security Lake for account {account_id}. In the Security Lake console, "
|
|
81
|
+
"navigate to Settings and enable Security Lake. Alternatively, use the AWS CLI command: "
|
|
82
|
+
f"aws securitylake create-data-lake --region {region}"
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
else:
|
|
87
|
+
self.findings.append(
|
|
88
|
+
self.create_finding(
|
|
89
|
+
status="PASS",
|
|
90
|
+
region=region,
|
|
91
|
+
resource_id=resource_id,
|
|
92
|
+
checked_value="Security Lake enabled",
|
|
93
|
+
actual_value=f"Security Lake is enabled for account {account_id}",
|
|
94
|
+
remediation="No remediation needed"
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return self.findings
|