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,233 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base class for Inspector security checks.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Optional, Dict, Any
|
|
5
|
+
from sraverify.core.check import SecurityCheck
|
|
6
|
+
from sraverify.services.inspector.client import InspectorClient
|
|
7
|
+
from sraverify.core.logging import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class InspectorCheck(SecurityCheck):
|
|
11
|
+
"""Base class for all Inspector security checks."""
|
|
12
|
+
|
|
13
|
+
# Class-level caches shared across all instances
|
|
14
|
+
_inspector_account_status = {}
|
|
15
|
+
_inspector_batch_account_status = {} # SRA-INSPECTOR-7
|
|
16
|
+
_inspector_delegated_admin = {}
|
|
17
|
+
_inspector_org_config = {}
|
|
18
|
+
_organization_members = {}
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
"""Initialize Inspector base check."""
|
|
22
|
+
super().__init__(
|
|
23
|
+
account_type="application", # Default, can be overridden in subclasses
|
|
24
|
+
service="Inspector",
|
|
25
|
+
resource_type="AWS::Inspector::Assessment"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def _setup_clients(self):
|
|
29
|
+
"""Set up Inspector clients for each region."""
|
|
30
|
+
# Clear existing clients
|
|
31
|
+
self._clients.clear()
|
|
32
|
+
# Set up new clients only if regions are initialized
|
|
33
|
+
if hasattr(self, 'regions') and self.regions:
|
|
34
|
+
for region in self.regions:
|
|
35
|
+
self._clients[region] = InspectorClient(region, session=self.session)
|
|
36
|
+
|
|
37
|
+
def get_client(self, region: str) -> Optional[InspectorClient]:
|
|
38
|
+
"""
|
|
39
|
+
Get Inspector client for a specific region.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
region: AWS region name
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
InspectorClient for the region or None if not available
|
|
46
|
+
"""
|
|
47
|
+
return self._clients.get(region)
|
|
48
|
+
|
|
49
|
+
def get_account_status(self, region: str) -> Dict[str, Any]:
|
|
50
|
+
"""
|
|
51
|
+
Get Inspector account status with caching.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
region: AWS region name
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Dictionary containing account status
|
|
58
|
+
"""
|
|
59
|
+
account_id = self.account_id
|
|
60
|
+
if not account_id:
|
|
61
|
+
logger.warning("Could not determine account ID")
|
|
62
|
+
return {}
|
|
63
|
+
|
|
64
|
+
# Check cache first
|
|
65
|
+
cache_key = f"{account_id}:{region}"
|
|
66
|
+
if cache_key in self.__class__._inspector_account_status:
|
|
67
|
+
logger.debug(f"Using cached Inspector account status for {cache_key}")
|
|
68
|
+
return self.__class__._inspector_account_status[cache_key]
|
|
69
|
+
|
|
70
|
+
client = self.get_client(region)
|
|
71
|
+
if not client:
|
|
72
|
+
logger.warning(f"No Inspector client available for region {region}")
|
|
73
|
+
return {}
|
|
74
|
+
|
|
75
|
+
# Get account status from client
|
|
76
|
+
response = client.batch_get_account_status(account_ids=[account_id])
|
|
77
|
+
|
|
78
|
+
# Extract the account status for the current account
|
|
79
|
+
account_status = {}
|
|
80
|
+
for status in response.get('accounts', []):
|
|
81
|
+
if status.get('accountId') == account_id:
|
|
82
|
+
# Restructure the account status to make it easier to access
|
|
83
|
+
account_status = {
|
|
84
|
+
'accountId': status.get('accountId'),
|
|
85
|
+
'state': status.get('state', {}),
|
|
86
|
+
# Extract resource states to top level for easier access in checks
|
|
87
|
+
'ec2': status.get('resourceState', {}).get('ec2', {}),
|
|
88
|
+
'ecr': status.get('resourceState', {}).get('ecr', {}),
|
|
89
|
+
'lambda': status.get('resourceState', {}).get('lambda', {}),
|
|
90
|
+
'lambdaCode': status.get('resourceState', {}).get('lambdaCode', {})
|
|
91
|
+
}
|
|
92
|
+
break
|
|
93
|
+
|
|
94
|
+
# Cache the result
|
|
95
|
+
self.__class__._inspector_account_status[cache_key] = account_status
|
|
96
|
+
logger.debug(f"Cached Inspector account status for {cache_key}")
|
|
97
|
+
|
|
98
|
+
return account_status
|
|
99
|
+
|
|
100
|
+
def get_delegated_admin(self, region: str) -> Dict[str, Any]:
|
|
101
|
+
"""
|
|
102
|
+
Get Inspector delegated admin with caching.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
region: AWS region name
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Dictionary containing delegated admin information
|
|
109
|
+
"""
|
|
110
|
+
# Check cache first
|
|
111
|
+
cache_key = f"{self.session.region_name}:{region}"
|
|
112
|
+
if cache_key in self.__class__._inspector_delegated_admin:
|
|
113
|
+
logger.debug(f"Using cached Inspector delegated admin for {cache_key}")
|
|
114
|
+
return self.__class__._inspector_delegated_admin[cache_key]
|
|
115
|
+
|
|
116
|
+
client = self.get_client(region)
|
|
117
|
+
if not client:
|
|
118
|
+
logger.warning(f"No Inspector client available for region {region}")
|
|
119
|
+
return {}
|
|
120
|
+
|
|
121
|
+
# Get delegated admin from client
|
|
122
|
+
response = client.get_delegated_admin_account()
|
|
123
|
+
|
|
124
|
+
# Cache the result
|
|
125
|
+
self.__class__._inspector_delegated_admin[cache_key] = response
|
|
126
|
+
logger.debug(f"Cached Inspector delegated admin for {cache_key}")
|
|
127
|
+
|
|
128
|
+
return response
|
|
129
|
+
|
|
130
|
+
def get_organization_members(self, region: str) -> List[Dict[str, Any]]:
|
|
131
|
+
"""
|
|
132
|
+
Get all AWS Organization member accounts with caching.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
region: AWS region name (not used for Organizations API call)
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
List of organization member accounts
|
|
139
|
+
"""
|
|
140
|
+
# Use the current session region for Organizations API call
|
|
141
|
+
current_region = self.session.region_name
|
|
142
|
+
|
|
143
|
+
# Check cache first
|
|
144
|
+
cache_key = f"{current_region}:organizations"
|
|
145
|
+
if cache_key in self.__class__._organization_members:
|
|
146
|
+
logger.debug(f"Using cached organization members for {cache_key}")
|
|
147
|
+
return self.__class__._organization_members[cache_key]
|
|
148
|
+
|
|
149
|
+
# Use the client for the current region
|
|
150
|
+
client = self.get_client(current_region)
|
|
151
|
+
if not client:
|
|
152
|
+
logger.warning(f"No Inspector client available for region {current_region}")
|
|
153
|
+
return []
|
|
154
|
+
|
|
155
|
+
# Get organization members from client
|
|
156
|
+
accounts = client.list_organization_accounts()
|
|
157
|
+
|
|
158
|
+
# Cache the result
|
|
159
|
+
self.__class__._organization_members[cache_key] = accounts
|
|
160
|
+
logger.debug(f"Cached {len(accounts)} organization members for {cache_key} (using current region)")
|
|
161
|
+
|
|
162
|
+
return accounts
|
|
163
|
+
|
|
164
|
+
def batch_get_account_status(self, region: str, account_ids: List[str]) -> Dict[str, Dict]:
|
|
165
|
+
"""
|
|
166
|
+
Get Inspector account status for multiple accounts with caching.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
region: AWS region name
|
|
170
|
+
account_ids: List of account IDs to check
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
Dictionary mapping account IDs to their status
|
|
174
|
+
"""
|
|
175
|
+
# Check cache first
|
|
176
|
+
cache_key = f"{self.session.region_name}:{region}:batch_status"
|
|
177
|
+
if cache_key in self.__class__._inspector_batch_account_status:
|
|
178
|
+
logger.debug(f"Using cached Inspector batch account status for {cache_key}")
|
|
179
|
+
return self.__class__._inspector_batch_account_status[cache_key]
|
|
180
|
+
|
|
181
|
+
client = self.get_client(region)
|
|
182
|
+
if not client:
|
|
183
|
+
logger.warning(f"No Inspector client available for region {region}")
|
|
184
|
+
return {}
|
|
185
|
+
|
|
186
|
+
# Process accounts in batches of 10 (API limit)
|
|
187
|
+
result = {}
|
|
188
|
+
for i in range(0, len(account_ids), 10):
|
|
189
|
+
batch = account_ids[i:i+10]
|
|
190
|
+
try:
|
|
191
|
+
response = client.batch_get_account_status(batch)
|
|
192
|
+
for account in response.get('accounts', []):
|
|
193
|
+
acc_id = account.get('accountId')
|
|
194
|
+
if acc_id:
|
|
195
|
+
result[acc_id] = account
|
|
196
|
+
except Exception as e:
|
|
197
|
+
logger.debug(f"Error getting batch account status in {region}: {e}")
|
|
198
|
+
|
|
199
|
+
# Cache the result
|
|
200
|
+
self.__class__._inspector_batch_account_status[cache_key] = result
|
|
201
|
+
logger.debug(f"Cached Inspector batch account status for {len(result)} accounts in {region}")
|
|
202
|
+
|
|
203
|
+
return result
|
|
204
|
+
|
|
205
|
+
def get_organization_configuration(self, region: str) -> Dict[str, Any]:
|
|
206
|
+
"""
|
|
207
|
+
Get Inspector organization configuration with caching.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
region: AWS region name
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
Dictionary containing organization configuration
|
|
214
|
+
"""
|
|
215
|
+
# Check cache first
|
|
216
|
+
cache_key = f"{self.session.region_name}:{region}"
|
|
217
|
+
if cache_key in self.__class__._inspector_org_config:
|
|
218
|
+
logger.debug(f"Using cached Inspector organization configuration for {cache_key}")
|
|
219
|
+
return self.__class__._inspector_org_config[cache_key]
|
|
220
|
+
|
|
221
|
+
client = self.get_client(region)
|
|
222
|
+
if not client:
|
|
223
|
+
logger.warning(f"No Inspector client available for region {region}")
|
|
224
|
+
return {}
|
|
225
|
+
|
|
226
|
+
# Get organization configuration from client
|
|
227
|
+
response = client.describe_organization_configuration()
|
|
228
|
+
|
|
229
|
+
# Cache the result
|
|
230
|
+
self.__class__._inspector_org_config[cache_key] = response
|
|
231
|
+
logger.debug(f"Cached Inspector organization configuration for {cache_key}")
|
|
232
|
+
|
|
233
|
+
return response
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SRA-INSPECTOR-01: Inspector Service Status.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from sraverify.services.inspector.base import InspectorCheck
|
|
6
|
+
from sraverify.core.logging import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SRA_INSPECTOR_01(InspectorCheck):
|
|
10
|
+
"""Check if Inspector service is enabled for the account."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
"""Initialize the check."""
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.check_id = "SRA-INSPECTOR-01"
|
|
16
|
+
self.check_name = "Inspector service is enabled"
|
|
17
|
+
self.account_type = "application"
|
|
18
|
+
self.severity = "HIGH"
|
|
19
|
+
self.description = (
|
|
20
|
+
"This check verifies whether Inspector service status for the account is enabled. "
|
|
21
|
+
"Amazon Inspector is a vulnerability management service that continuously scans your AWS "
|
|
22
|
+
"workloads for software vulnerabilities and unintended network exposure."
|
|
23
|
+
)
|
|
24
|
+
self.check_logic = (
|
|
25
|
+
"Check runs inspector2 batch-get-account-status. Check PASS if response state status = Enabled"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
29
|
+
"""
|
|
30
|
+
Execute the check.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
List of findings
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
for region in self.regions:
|
|
37
|
+
# Get account status using the base class method with caching
|
|
38
|
+
account_status = self.get_account_status(region)
|
|
39
|
+
|
|
40
|
+
# Check if state status is enabled
|
|
41
|
+
state_status = account_status.get('state', {}).get('status')
|
|
42
|
+
|
|
43
|
+
if not account_status or state_status != 'ENABLED':
|
|
44
|
+
self.findings.append(
|
|
45
|
+
self.create_finding(
|
|
46
|
+
status="FAIL",
|
|
47
|
+
region=region,
|
|
48
|
+
resource_id=f"inspector2/{self.account_id}",
|
|
49
|
+
checked_value="Inspector state status: ENABLED",
|
|
50
|
+
actual_value=f"Inspector state status: {state_status if state_status else 'NOT_ENABLED'}",
|
|
51
|
+
remediation=(
|
|
52
|
+
"Enable Amazon Inspector for your account using the AWS Console or CLI command: "
|
|
53
|
+
f"aws inspector2 enable --account-ids {self.account_id} --resource-types EC2 ECR LAMBDA LAMBDA_CODE --region {region}"
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
self.findings.append(
|
|
59
|
+
self.create_finding(
|
|
60
|
+
status="PASS",
|
|
61
|
+
region=region,
|
|
62
|
+
resource_id=f"inspector2/{self.account_id}",
|
|
63
|
+
checked_value="Inspector state status: ENABLED",
|
|
64
|
+
actual_value=f"Inspector state status: {state_status}",
|
|
65
|
+
remediation="No remediation needed"
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return self.findings
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SRA-INSPECTOR-02: Inspector EC2 Vulnerability Scanning.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from sraverify.services.inspector.base import InspectorCheck
|
|
6
|
+
from sraverify.core.logging import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SRA_INSPECTOR_02(InspectorCheck):
|
|
10
|
+
"""Check if Inspector EC2 vulnerability scanning is enabled for the account."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
"""Initialize the check."""
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.check_id = "SRA-INSPECTOR-02"
|
|
16
|
+
self.check_name = "Inspector EC2 vulnerability scanning is enabled"
|
|
17
|
+
self.account_type = "application"
|
|
18
|
+
self.severity = "HIGH"
|
|
19
|
+
self.description = (
|
|
20
|
+
"This check verifies whether Inspector EC2 vulnerability scanning feature is enabled. "
|
|
21
|
+
"Inspector automatically discovers EC2 instances and scans for software vulnerability."
|
|
22
|
+
)
|
|
23
|
+
self.check_logic = (
|
|
24
|
+
"Check runs inspector2 batch-get-account-status. Check PASS if response ec2 status = ENABLED"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
28
|
+
"""
|
|
29
|
+
Execute the check.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
List of findings
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
for region in self.regions:
|
|
36
|
+
# Get account status using the base class method with caching
|
|
37
|
+
account_status = self.get_account_status(region)
|
|
38
|
+
|
|
39
|
+
# Check if EC2 scanning is enabled
|
|
40
|
+
ec2_status = account_status.get('ec2', {}).get('status')
|
|
41
|
+
|
|
42
|
+
if not account_status or ec2_status != 'ENABLED':
|
|
43
|
+
self.findings.append(
|
|
44
|
+
self.create_finding(
|
|
45
|
+
status="FAIL",
|
|
46
|
+
region=region,
|
|
47
|
+
resource_id=f"inspector2/{self.account_id}/ec2",
|
|
48
|
+
checked_value="Inspector EC2 scanning: ENABLED",
|
|
49
|
+
actual_value=f"Inspector EC2 scanning: {ec2_status if ec2_status else 'NOT_ENABLED'}",
|
|
50
|
+
remediation=(
|
|
51
|
+
"Enable Amazon Inspector EC2 scanning for your account using the AWS Console or CLI command: "
|
|
52
|
+
f"aws inspector2 enable --account-ids {self.account_id} --resource-types EC2 --region {region}"
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
else:
|
|
57
|
+
self.findings.append(
|
|
58
|
+
self.create_finding(
|
|
59
|
+
status="PASS",
|
|
60
|
+
region=region,
|
|
61
|
+
resource_id=f"inspector2/{self.account_id}/ec2",
|
|
62
|
+
checked_value="Inspector EC2 scanning: ENABLED",
|
|
63
|
+
actual_value=f"Inspector EC2 scanning: {ec2_status}",
|
|
64
|
+
remediation="No remediation needed"
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return self.findings
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SRA-INSPECTOR-03: Inspector ECR Image Vulnerability Scanning.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from sraverify.services.inspector.base import InspectorCheck
|
|
6
|
+
from sraverify.core.logging import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SRA_INSPECTOR_03(InspectorCheck):
|
|
10
|
+
"""Check if Inspector ECR image vulnerability scanning is enabled for the account."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
"""Initialize the check."""
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.check_id = "SRA-INSPECTOR-03"
|
|
16
|
+
self.check_name = "Inspector ECR image vulnerability scanning is enabled"
|
|
17
|
+
self.account_type = "application"
|
|
18
|
+
self.severity = "HIGH"
|
|
19
|
+
self.description = (
|
|
20
|
+
"This check verifies whether Inspector ECR image vulnerability scanning feature is enabled. "
|
|
21
|
+
"Amazon Inspector scans container images stored in Amazon ECR for software vulnerabilities to generate findings."
|
|
22
|
+
)
|
|
23
|
+
self.check_logic = (
|
|
24
|
+
"Check runs inspector2 batch-get-account-status. Check PASS if ecr status = ENABLED"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
28
|
+
"""
|
|
29
|
+
Execute the check.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
List of findings
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
for region in self.regions:
|
|
36
|
+
# Get account status using the base class method with caching
|
|
37
|
+
account_status = self.get_account_status(region)
|
|
38
|
+
|
|
39
|
+
# Check if ECR scanning is enabled
|
|
40
|
+
ecr_status = account_status.get('ecr', {}).get('status')
|
|
41
|
+
|
|
42
|
+
if not account_status or ecr_status != 'ENABLED':
|
|
43
|
+
self.findings.append(
|
|
44
|
+
self.create_finding(
|
|
45
|
+
status="FAIL",
|
|
46
|
+
region=region,
|
|
47
|
+
resource_id=f"inspector2/{self.account_id}/ecr",
|
|
48
|
+
checked_value="Inspector ECR scanning: ENABLED",
|
|
49
|
+
actual_value=f"Inspector ECR scanning: {ecr_status if ecr_status else 'NOT_ENABLED'}",
|
|
50
|
+
remediation=(
|
|
51
|
+
"Enable Amazon Inspector ECR scanning for your account using the AWS Console or CLI command: "
|
|
52
|
+
f"aws inspector2 enable --account-ids {self.account_id} --resource-types ECR --region {region}"
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
else:
|
|
57
|
+
self.findings.append(
|
|
58
|
+
self.create_finding(
|
|
59
|
+
status="PASS",
|
|
60
|
+
region=region,
|
|
61
|
+
resource_id=f"inspector2/{self.account_id}/ecr",
|
|
62
|
+
checked_value="Inspector ECR scanning: ENABLED",
|
|
63
|
+
actual_value=f"Inspector ECR scanning: {ecr_status}",
|
|
64
|
+
remediation="No remediation needed"
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
return self.findings
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SRA-INSPECTOR-04: Inspector Lambda Function and Layers Vulnerability Scanning.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from sraverify.services.inspector.base import InspectorCheck
|
|
6
|
+
from sraverify.core.logging import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SRA_INSPECTOR_04(InspectorCheck):
|
|
10
|
+
"""Check if Inspector Lambda function and layers vulnerability scanning is enabled for the account."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
"""Initialize the check."""
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.check_id = "SRA-INSPECTOR-04"
|
|
16
|
+
self.check_name = "Inspector Lambda function and layers vulnerability scanning is enabled"
|
|
17
|
+
self.account_type = "application"
|
|
18
|
+
self.severity = "HIGH"
|
|
19
|
+
self.description = (
|
|
20
|
+
"This check verifies whether Inspector Lambda function and layers for package and code vulnerability. "
|
|
21
|
+
"Amazon Inspector monitors each Lambda function throughout its lifetime until it's either deleted or excluded from scanning."
|
|
22
|
+
)
|
|
23
|
+
self.check_logic = (
|
|
24
|
+
"Check runs inspector2 batch-get-account-status. Check PASS if lambda status = ENABLED AND lambdaCode status = ENABLED"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
28
|
+
"""
|
|
29
|
+
Execute the check.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
List of findings
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
for region in self.regions:
|
|
36
|
+
# Get account status using the base class method with caching
|
|
37
|
+
account_status = self.get_account_status(region)
|
|
38
|
+
|
|
39
|
+
# Check if Lambda and LambdaCode scanning are enabled
|
|
40
|
+
lambda_status = account_status.get('lambda', {}).get('status')
|
|
41
|
+
lambda_code_status = account_status.get('lambdaCode', {}).get('status')
|
|
42
|
+
|
|
43
|
+
if not account_status or lambda_status != 'ENABLED' or lambda_code_status != 'ENABLED':
|
|
44
|
+
self.findings.append(
|
|
45
|
+
self.create_finding(
|
|
46
|
+
status="FAIL",
|
|
47
|
+
region=region,
|
|
48
|
+
resource_id=f"inspector2/{self.account_id}/lambda",
|
|
49
|
+
checked_value="Inspector Lambda scanning: ENABLED, LambdaCode scanning: ENABLED",
|
|
50
|
+
actual_value=f"Inspector Lambda scanning: {lambda_status if lambda_status else 'NOT_ENABLED'}, "
|
|
51
|
+
f"LambdaCode scanning: {lambda_code_status if lambda_code_status else 'NOT_ENABLED'}",
|
|
52
|
+
remediation=(
|
|
53
|
+
"Enable Amazon Inspector Lambda and LambdaCode scanning for your account using the AWS Console or CLI command: "
|
|
54
|
+
f"aws inspector2 enable --account-ids {self.account_id} --resource-types LAMBDA LAMBDA_CODE --region {region}"
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
else:
|
|
59
|
+
self.findings.append(
|
|
60
|
+
self.create_finding(
|
|
61
|
+
status="PASS",
|
|
62
|
+
region=region,
|
|
63
|
+
resource_id=f"inspector2/{self.account_id}/lambda",
|
|
64
|
+
checked_value="Inspector Lambda scanning: ENABLED, LambdaCode scanning: ENABLED",
|
|
65
|
+
actual_value=f"Inspector Lambda scanning: {lambda_status}, LambdaCode scanning: {lambda_code_status}",
|
|
66
|
+
remediation="No remediation needed"
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return self.findings
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SRA-INSPECTOR-05: Inspector Delegated Admin Account is Configured.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from sraverify.services.inspector.base import InspectorCheck
|
|
6
|
+
from sraverify.core.logging import logger
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SRA_INSPECTOR_05(InspectorCheck):
|
|
10
|
+
"""Check if Inspector delegated admin account is configured."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
"""Initialize the check."""
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.check_id = "SRA-INSPECTOR-05"
|
|
16
|
+
self.check_name = "Inspector delegated admin account is configured"
|
|
17
|
+
self.account_type = "management"
|
|
18
|
+
self.severity = "HIGH"
|
|
19
|
+
self.description = (
|
|
20
|
+
"This check verifies whether a delegated administrator account is configured for Amazon Inspector. "
|
|
21
|
+
"A delegated administrator can manage Inspector findings across all accounts in the organization."
|
|
22
|
+
)
|
|
23
|
+
self.check_logic = (
|
|
24
|
+
"Check runs inspector2 get-delegated-admin-account. Check PASS if response contains delegatedAdmin"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
28
|
+
"""
|
|
29
|
+
Execute the check.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
List of findings
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# Check each region separately
|
|
36
|
+
for region in self.regions:
|
|
37
|
+
# Get delegated admin account for this region
|
|
38
|
+
delegated_admin_response = self.get_delegated_admin(region)
|
|
39
|
+
delegated_admin = delegated_admin_response.get('delegatedAdmin', {})
|
|
40
|
+
delegated_admin_id = delegated_admin.get('accountId')
|
|
41
|
+
|
|
42
|
+
if not delegated_admin_id:
|
|
43
|
+
self.findings.append(
|
|
44
|
+
self.create_finding(
|
|
45
|
+
status="FAIL",
|
|
46
|
+
region=region,
|
|
47
|
+
resource_id=f"inspector2/{region}/delegated-admin",
|
|
48
|
+
checked_value="Inspector delegated admin account is configured",
|
|
49
|
+
actual_value="No delegated admin account is configured",
|
|
50
|
+
remediation=(
|
|
51
|
+
"Configure a delegated admin account for Inspector using the AWS Console or CLI command: "
|
|
52
|
+
f"aws organizations register-delegated-administrator --account-id <AUDIT_ACCOUNT_ID> "
|
|
53
|
+
f"--service-principal inspector2.amazonaws.com --region {region}"
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
else:
|
|
58
|
+
self.findings.append(
|
|
59
|
+
self.create_finding(
|
|
60
|
+
status="PASS",
|
|
61
|
+
region=region,
|
|
62
|
+
resource_id=f"inspector2/{region}/delegated-admin",
|
|
63
|
+
checked_value="Inspector delegated admin account is configured",
|
|
64
|
+
actual_value=f"Delegated admin account {delegated_admin_id} is configured",
|
|
65
|
+
remediation="No remediation needed"
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return self.findings
|