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,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SRA-CLOUDTRAIL-12: CloudTrail Delegated Administrator 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_12(CloudTrailCheck):
|
|
10
|
+
"""Check if CloudTrail service administration is delegated out of AWS Organization management account."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
"""Initialize the check."""
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.check_id = "SRA-CLOUDTRAIL-12"
|
|
16
|
+
self.check_name = "Delegated Administrator set for CloudTrail"
|
|
17
|
+
self.account_type = "management"
|
|
18
|
+
self.severity = "MEDIUM"
|
|
19
|
+
self.description = (
|
|
20
|
+
"This check verifies whether CloudTrail service administration is delegated out of AWS Organization "
|
|
21
|
+
"management account. The delegated administrator has permissions to create and manage analyzers "
|
|
22
|
+
"with the AWS organization as the zone of trust."
|
|
23
|
+
)
|
|
24
|
+
self.check_logic = (
|
|
25
|
+
"Check if there is at least one delegated administrator for CloudTrail service."
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
29
|
+
"""
|
|
30
|
+
Execute the check.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
List of findings
|
|
34
|
+
"""
|
|
35
|
+
findings = []
|
|
36
|
+
|
|
37
|
+
# Get delegated administrators for CloudTrail
|
|
38
|
+
# This will use the cache if available or make API calls if needed
|
|
39
|
+
delegated_admins = self.get_delegated_administrators()
|
|
40
|
+
|
|
41
|
+
if not delegated_admins:
|
|
42
|
+
findings.append(
|
|
43
|
+
self.create_finding(
|
|
44
|
+
status="FAIL",
|
|
45
|
+
region="global",
|
|
46
|
+
resource_id=f"organization/{self.account_id}",
|
|
47
|
+
checked_value="At least one delegated administrator for CloudTrail",
|
|
48
|
+
actual_value="No delegated administrator configured for CloudTrail",
|
|
49
|
+
remediation=(
|
|
50
|
+
"Register a delegated administrator for CloudTrail using the AWS CLI command: "
|
|
51
|
+
"aws organizations register-delegated-administrator "
|
|
52
|
+
"--account-id ACCOUNT_ID --service-principal cloudtrail.amazonaws.com"
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
return findings
|
|
57
|
+
|
|
58
|
+
# If we have delegated administrators, create a PASS finding for each one
|
|
59
|
+
for admin in delegated_admins:
|
|
60
|
+
admin_id = admin.get('Id', 'Unknown')
|
|
61
|
+
admin_name = admin.get('Name', 'Unknown')
|
|
62
|
+
|
|
63
|
+
# Create a resource ID that includes the delegated admin info
|
|
64
|
+
resource_id = f"cloudtrail arn has delegated administrator set to {admin_id}"
|
|
65
|
+
|
|
66
|
+
findings.append(
|
|
67
|
+
self.create_finding(
|
|
68
|
+
status="PASS",
|
|
69
|
+
region="global",
|
|
70
|
+
resource_id=resource_id,
|
|
71
|
+
checked_value="At least one delegated administrator for CloudTrail",
|
|
72
|
+
actual_value=f"CloudTrail has delegated administrator: {admin_id} ({admin_name})",
|
|
73
|
+
remediation="No remediation needed"
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
return findings
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SRA-CLOUDTRAIL-13: CloudTrail Delegated Administrator is the Audit Account.
|
|
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_13(CloudTrailCheck):
|
|
10
|
+
"""Check if CloudTrail delegated administrator is the Audit account."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
"""Initialize the check."""
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.check_id = "SRA-CLOUDTRAIL-13"
|
|
16
|
+
self.check_name = "The audit account is the Delegated Administrator set for CloudTrail"
|
|
17
|
+
self.account_type = "management"
|
|
18
|
+
self.severity = "HIGH"
|
|
19
|
+
self.description = (
|
|
20
|
+
"This check verifies whether CloudTrail delegated admin account is the audit account of your AWS organization. "
|
|
21
|
+
"Audit account is dedicated to operating security services, monitoring AWS accounts, and "
|
|
22
|
+
"automating security alerting and response. CloudTrail helps monitor API activities across "
|
|
23
|
+
"all your AWS accounts and regions."
|
|
24
|
+
)
|
|
25
|
+
self.check_logic = (
|
|
26
|
+
"Check if the delegated administrator account matches any of the specified Audit account IDs."
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
30
|
+
"""
|
|
31
|
+
Execute the check.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
List of findings
|
|
35
|
+
"""
|
|
36
|
+
findings = []
|
|
37
|
+
|
|
38
|
+
# Get delegated administrators for CloudTrail
|
|
39
|
+
# This will use the cache if available or make API calls if needed
|
|
40
|
+
delegated_admins = self.get_delegated_administrators()
|
|
41
|
+
|
|
42
|
+
if not delegated_admins:
|
|
43
|
+
findings.append(
|
|
44
|
+
self.create_finding(
|
|
45
|
+
status="FAIL",
|
|
46
|
+
region="global",
|
|
47
|
+
resource_id=f"organization/{self.account_id}",
|
|
48
|
+
checked_value="CloudTrail delegated administrator is an Audit account",
|
|
49
|
+
actual_value="No delegated administrator configured for CloudTrail",
|
|
50
|
+
remediation=(
|
|
51
|
+
"Register an Audit account as delegated administrator for CloudTrail using the AWS CLI command: "
|
|
52
|
+
"aws organizations register-delegated-administrator "
|
|
53
|
+
"--account-id AUDIT_ACCOUNT_ID --service-principal cloudtrail.amazonaws.com"
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
return findings
|
|
58
|
+
|
|
59
|
+
# Check if audit_accounts is provided via _audit_accounts attribute
|
|
60
|
+
audit_accounts = []
|
|
61
|
+
if hasattr(self, '_audit_accounts') and self._audit_accounts:
|
|
62
|
+
audit_accounts = self._audit_accounts
|
|
63
|
+
|
|
64
|
+
if not audit_accounts:
|
|
65
|
+
findings.append(
|
|
66
|
+
self.create_finding(
|
|
67
|
+
status="ERROR",
|
|
68
|
+
region="global",
|
|
69
|
+
resource_id=f"organization/{self.account_id}",
|
|
70
|
+
checked_value="CloudTrail delegated administrator is an Audit account",
|
|
71
|
+
actual_value="Audit Account ID not provided",
|
|
72
|
+
remediation="Provide the Audit account IDs using --audit-account flag"
|
|
73
|
+
)
|
|
74
|
+
)
|
|
75
|
+
return findings
|
|
76
|
+
|
|
77
|
+
# Check if any of the delegated administrators is an Audit account
|
|
78
|
+
for admin in delegated_admins:
|
|
79
|
+
admin_id = admin.get('Id', 'Unknown')
|
|
80
|
+
admin_name = admin.get('Name', 'Unknown')
|
|
81
|
+
|
|
82
|
+
# Create a resource ID that includes the delegated admin and audit account info
|
|
83
|
+
resource_id = f"cloudtrail arn has delegated administrator set to {admin_id}, audit account is {audit_accounts[0]}, {admin_id in audit_accounts}"
|
|
84
|
+
|
|
85
|
+
if admin_id in audit_accounts:
|
|
86
|
+
# This delegated admin is in the audit accounts list
|
|
87
|
+
findings.append(
|
|
88
|
+
self.create_finding(
|
|
89
|
+
status="PASS",
|
|
90
|
+
region="global",
|
|
91
|
+
resource_id=resource_id,
|
|
92
|
+
checked_value=f"CloudTrail delegated administrator is an Audit account ({', '.join(audit_accounts)})",
|
|
93
|
+
actual_value=f"CloudTrail delegated administrator {admin_id} ({admin_name}) is an Audit account",
|
|
94
|
+
remediation="No remediation needed"
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
else:
|
|
98
|
+
# This delegated admin is not in the audit accounts list
|
|
99
|
+
findings.append(
|
|
100
|
+
self.create_finding(
|
|
101
|
+
status="FAIL",
|
|
102
|
+
region="global",
|
|
103
|
+
resource_id=resource_id,
|
|
104
|
+
checked_value=f"CloudTrail delegated administrator is an Audit account ({', '.join(audit_accounts)})",
|
|
105
|
+
actual_value=(
|
|
106
|
+
f"CloudTrail delegated administrator {admin_id} ({admin_name}) "
|
|
107
|
+
f"is not in the specified Audit accounts ({', '.join(audit_accounts)})"
|
|
108
|
+
),
|
|
109
|
+
remediation=(
|
|
110
|
+
"Deregister the current delegated administrator and register an Audit account "
|
|
111
|
+
"as delegated administrator for CloudTrail using the AWS CLI commands: "
|
|
112
|
+
f"aws organizations deregister-delegated-administrator "
|
|
113
|
+
f"--account-id {admin_id} --service-principal cloudtrail.amazonaws.com && "
|
|
114
|
+
"aws organizations register-delegated-administrator "
|
|
115
|
+
f"--account-id {audit_accounts[0]} --service-principal cloudtrail.amazonaws.com"
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return findings
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CloudTrail client for interacting with AWS CloudTrail service.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Dict, List, Optional, Any
|
|
5
|
+
import boto3
|
|
6
|
+
from botocore.exceptions import ClientError
|
|
7
|
+
from sraverify.core.logging import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CloudTrailClient:
|
|
11
|
+
"""Client for interacting with AWS CloudTrail service."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, region: str, session: Optional[boto3.Session] = None):
|
|
14
|
+
"""
|
|
15
|
+
Initialize CloudTrail 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('cloudtrail', region_name=region)
|
|
24
|
+
self.org_client = self.session.client('organizations', region_name=region)
|
|
25
|
+
|
|
26
|
+
def describe_trails(self, trail_name_list: Optional[List[str]] = None, include_shadow_trails: bool = True) -> List[Dict[str, Any]]:
|
|
27
|
+
"""
|
|
28
|
+
Describe one or more trails.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
trail_name_list: List of trail names to describe (if None, all trails are described)
|
|
32
|
+
include_shadow_trails: Include shadow trails in the response
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
List of trail descriptions
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
params = {}
|
|
39
|
+
if trail_name_list:
|
|
40
|
+
params['trailNameList'] = trail_name_list
|
|
41
|
+
params['includeShadowTrails'] = include_shadow_trails
|
|
42
|
+
|
|
43
|
+
logger.debug(f"Describing trails in {self.region} with params: {params}")
|
|
44
|
+
response = self.client.describe_trails(**params)
|
|
45
|
+
return response.get('trailList', [])
|
|
46
|
+
except ClientError as e:
|
|
47
|
+
logger.error(f"Error describing trails in {self.region}: {e}")
|
|
48
|
+
return []
|
|
49
|
+
except Exception as e:
|
|
50
|
+
logger.error(f"Unexpected error describing trails in {self.region}: {e}")
|
|
51
|
+
return []
|
|
52
|
+
|
|
53
|
+
def get_trail_status(self, trail_arn: str) -> Dict[str, Any]:
|
|
54
|
+
"""
|
|
55
|
+
Get the status of a trail.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
trail_arn: ARN of the trail
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Trail status
|
|
62
|
+
"""
|
|
63
|
+
try:
|
|
64
|
+
logger.debug(f"Getting status for trail {trail_arn} in {self.region}")
|
|
65
|
+
response = self.client.get_trail_status(Name=trail_arn)
|
|
66
|
+
return response
|
|
67
|
+
except ClientError as e:
|
|
68
|
+
logger.error(f"Error getting trail status for {trail_arn} in {self.region}: {e}")
|
|
69
|
+
return {}
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.error(f"Unexpected error getting trail status for {trail_arn} in {self.region}: {e}")
|
|
72
|
+
return {}
|
|
73
|
+
|
|
74
|
+
def list_delegated_administrators(self, service_principal: str = "cloudtrail.amazonaws.com") -> List[Dict[str, Any]]:
|
|
75
|
+
"""
|
|
76
|
+
List delegated administrators for CloudTrail.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
service_principal: Service principal to check for delegated administrators
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
List of delegated administrators
|
|
83
|
+
"""
|
|
84
|
+
try:
|
|
85
|
+
logger.debug(f"Listing delegated administrators for {service_principal} in {self.region}")
|
|
86
|
+
response = self.org_client.list_delegated_administrators(ServicePrincipal=service_principal)
|
|
87
|
+
delegated_admins = response.get('DelegatedAdministrators', [])
|
|
88
|
+
logger.debug(f"Found {len(delegated_admins)} delegated administrators for {service_principal}")
|
|
89
|
+
for admin in delegated_admins:
|
|
90
|
+
logger.debug(f"Delegated admin: {admin.get('Id')} - {admin.get('Name')}")
|
|
91
|
+
return delegated_admins
|
|
92
|
+
except ClientError as e:
|
|
93
|
+
logger.error(f"Error listing delegated administrators for {service_principal}: {e}")
|
|
94
|
+
return []
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.error(f"Unexpected error listing delegated administrators: {e}")
|
|
97
|
+
return []
|
|
98
|
+
|
|
99
|
+
def get_account_id(self) -> Optional[str]:
|
|
100
|
+
"""
|
|
101
|
+
Get the current account ID.
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
Current account ID or None if not available
|
|
105
|
+
"""
|
|
106
|
+
try:
|
|
107
|
+
logger.debug(f"Getting current account ID in {self.region}")
|
|
108
|
+
sts_client = self.session.client("sts")
|
|
109
|
+
response = sts_client.get_caller_identity()
|
|
110
|
+
account_id = response["Account"]
|
|
111
|
+
logger.debug(f"Current account ID: {account_id}")
|
|
112
|
+
return account_id
|
|
113
|
+
except ClientError as e:
|
|
114
|
+
logger.error(f"Error getting current account ID: {e}")
|
|
115
|
+
return None
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.error(f"Unexpected error getting current account ID: {e}")
|
|
118
|
+
return None
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AWS Config security checks.
|
|
3
|
+
"""
|
|
4
|
+
from sraverify.services.config.checks.sra_config_01 import SRA_CONFIG_01
|
|
5
|
+
from sraverify.services.config.checks.sra_config_02 import SRA_CONFIG_02
|
|
6
|
+
from sraverify.services.config.checks.sra_config_03 import SRA_CONFIG_03
|
|
7
|
+
from sraverify.services.config.checks.sra_config_04 import SRA_CONFIG_04
|
|
8
|
+
from sraverify.services.config.checks.sra_config_05 import SRA_CONFIG_05
|
|
9
|
+
from sraverify.services.config.checks.sra_config_06 import SRA_CONFIG_06
|
|
10
|
+
from sraverify.services.config.checks.sra_config_07 import SRA_CONFIG_07
|
|
11
|
+
from sraverify.services.config.checks.sra_config_08 import SRA_CONFIG_08
|
|
12
|
+
from sraverify.services.config.checks.sra_config_09 import SRA_CONFIG_09
|
|
13
|
+
|
|
14
|
+
# Register checks
|
|
15
|
+
CHECKS = {
|
|
16
|
+
"SRA-CONFIG-01": SRA_CONFIG_01,
|
|
17
|
+
"SRA-CONFIG-02": SRA_CONFIG_02,
|
|
18
|
+
"SRA-CONFIG-03": SRA_CONFIG_03,
|
|
19
|
+
"SRA-CONFIG-04": SRA_CONFIG_04,
|
|
20
|
+
"SRA-CONFIG-05": SRA_CONFIG_05,
|
|
21
|
+
"SRA-CONFIG-06": SRA_CONFIG_06,
|
|
22
|
+
"SRA-CONFIG-07": SRA_CONFIG_07,
|
|
23
|
+
"SRA-CONFIG-08": SRA_CONFIG_08,
|
|
24
|
+
"SRA-CONFIG-09": SRA_CONFIG_09,
|
|
25
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base class for AWS Config security checks.
|
|
3
|
+
"""
|
|
4
|
+
from typing import List, Optional, Dict, Any
|
|
5
|
+
from sraverify.core.check import SecurityCheck
|
|
6
|
+
from sraverify.services.config.client import ConfigClient
|
|
7
|
+
from sraverify.core.logging import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ConfigCheck(SecurityCheck):
|
|
11
|
+
"""Base class for all AWS Config security checks."""
|
|
12
|
+
|
|
13
|
+
# Class-level caches shared across all instances
|
|
14
|
+
_config_recorder_status_cache = {}
|
|
15
|
+
_config_delivery_channel_status_cache = {}
|
|
16
|
+
_config_organization_aggregator = {}
|
|
17
|
+
_config_delivery_channel_cache = {}
|
|
18
|
+
_config_delegated_admin_cache = {}
|
|
19
|
+
|
|
20
|
+
# Config service principals
|
|
21
|
+
CONFIG_SERVICE_PRINCIPALS = [
|
|
22
|
+
"config.amazonaws.com",
|
|
23
|
+
"config-multiaccountsetup.amazonaws.com"
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
def __init__(self):
|
|
27
|
+
"""Initialize Config base check."""
|
|
28
|
+
super().__init__(
|
|
29
|
+
account_type="account", # Default to account, can be overridden in child classes
|
|
30
|
+
service="Config",
|
|
31
|
+
resource_type="AWS::Config::ConfigurationRecorder"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def _setup_clients(self):
|
|
35
|
+
"""Set up Config clients for each region."""
|
|
36
|
+
# Clear existing clients
|
|
37
|
+
self._clients.clear()
|
|
38
|
+
# Set up new clients only if regions are initialized
|
|
39
|
+
if hasattr(self, 'regions') and self.regions:
|
|
40
|
+
for region in self.regions:
|
|
41
|
+
self._clients[region] = ConfigClient(region, session=self.session)
|
|
42
|
+
|
|
43
|
+
def get_client(self, region: str) -> Optional[ConfigClient]:
|
|
44
|
+
"""
|
|
45
|
+
Get Config client for a specific region.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
region: AWS region name
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
ConfigClient for the region or None if not available
|
|
52
|
+
"""
|
|
53
|
+
return self._clients.get(region)
|
|
54
|
+
|
|
55
|
+
def get_configuration_recorders(self, region: str) -> List[Dict[str, Any]]:
|
|
56
|
+
"""
|
|
57
|
+
Get configuration recorders for a specific region.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
region: AWS region name
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
List of configuration recorders
|
|
64
|
+
"""
|
|
65
|
+
# Get client for the region
|
|
66
|
+
client = self.get_client(region)
|
|
67
|
+
if not client:
|
|
68
|
+
logger.warning(f"No Config client available for region {region}")
|
|
69
|
+
return []
|
|
70
|
+
|
|
71
|
+
# Get configuration recorders from client
|
|
72
|
+
recorders = client.describe_configuration_recorders()
|
|
73
|
+
logger.debug(f"Found {len(recorders)} configuration recorders for {region}")
|
|
74
|
+
|
|
75
|
+
return recorders
|
|
76
|
+
|
|
77
|
+
def get_configuration_recorder_status(self, region: str) -> List[Dict[str, Any]]:
|
|
78
|
+
"""
|
|
79
|
+
Get configuration recorder status for a specific region with caching.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
region: AWS region name
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
List of configuration recorder statuses
|
|
86
|
+
"""
|
|
87
|
+
# Check cache first
|
|
88
|
+
cache_key = f"{region}:{self.session.region_name}"
|
|
89
|
+
if cache_key in self.__class__._config_recorder_status_cache:
|
|
90
|
+
logger.debug(f"Using cached configuration recorder status for {region}")
|
|
91
|
+
return self.__class__._config_recorder_status_cache[cache_key]
|
|
92
|
+
|
|
93
|
+
# Get client for the region
|
|
94
|
+
client = self.get_client(region)
|
|
95
|
+
if not client:
|
|
96
|
+
logger.warning(f"No Config client available for region {region}")
|
|
97
|
+
return []
|
|
98
|
+
|
|
99
|
+
# Get configuration recorder status from client
|
|
100
|
+
statuses = client.describe_configuration_recorder_status()
|
|
101
|
+
|
|
102
|
+
# Cache the results - store the complete response
|
|
103
|
+
self.__class__._config_recorder_status_cache[cache_key] = statuses
|
|
104
|
+
logger.debug(f"Cached {len(statuses)} configuration recorder statuses for {region}")
|
|
105
|
+
|
|
106
|
+
return statuses
|
|
107
|
+
|
|
108
|
+
def get_delivery_channels(self, region: str) -> List[Dict[str, Any]]:
|
|
109
|
+
"""
|
|
110
|
+
Get delivery channels for a specific region.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
region: AWS region name
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
List of delivery channels
|
|
117
|
+
"""
|
|
118
|
+
# Check cache first
|
|
119
|
+
cache_key = f"{region}:{self.session.region_name}"
|
|
120
|
+
if cache_key in self.__class__._config_delivery_channel_cache:
|
|
121
|
+
logger.debug(f"Using cached delivery channels for {region}")
|
|
122
|
+
return self.__class__._config_delivery_channel_cache[cache_key]
|
|
123
|
+
|
|
124
|
+
# Get client for the region
|
|
125
|
+
client = self.get_client(region)
|
|
126
|
+
if not client:
|
|
127
|
+
logger.warning(f"No Config client available for region {region}")
|
|
128
|
+
return []
|
|
129
|
+
|
|
130
|
+
# Get delivery channels from client
|
|
131
|
+
channels = client.describe_delivery_channels()
|
|
132
|
+
|
|
133
|
+
# Cache the results
|
|
134
|
+
self.__class__._config_delivery_channel_cache[cache_key] = channels
|
|
135
|
+
logger.debug(f"Cached {len(channels)} delivery channels for {region}")
|
|
136
|
+
|
|
137
|
+
return channels
|
|
138
|
+
|
|
139
|
+
def get_delivery_channel_status(self, region: str) -> List[Dict[str, Any]]:
|
|
140
|
+
"""
|
|
141
|
+
Get delivery channel status for a specific region with caching.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
region: AWS region name
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
List of delivery channel statuses
|
|
148
|
+
"""
|
|
149
|
+
# Check cache first
|
|
150
|
+
cache_key = f"{region}:{self.session.region_name}"
|
|
151
|
+
if cache_key in self.__class__._config_delivery_channel_status_cache:
|
|
152
|
+
logger.debug(f"Using cached delivery channel status for {region}")
|
|
153
|
+
return self.__class__._config_delivery_channel_status_cache[cache_key]
|
|
154
|
+
|
|
155
|
+
# Get client for the region
|
|
156
|
+
client = self.get_client(region)
|
|
157
|
+
if not client:
|
|
158
|
+
logger.warning(f"No Config client available for region {region}")
|
|
159
|
+
return []
|
|
160
|
+
|
|
161
|
+
# Get delivery channel status from client
|
|
162
|
+
statuses = client.describe_delivery_channel_status()
|
|
163
|
+
|
|
164
|
+
# Cache the results - store the complete response
|
|
165
|
+
self.__class__._config_delivery_channel_status_cache[cache_key] = statuses
|
|
166
|
+
logger.debug(f"Cached {len(statuses)} delivery channel statuses for {region}")
|
|
167
|
+
|
|
168
|
+
return statuses
|
|
169
|
+
|
|
170
|
+
def get_configuration_aggregators(self, region: str) -> List[Dict[str, Any]]:
|
|
171
|
+
"""
|
|
172
|
+
Get configuration aggregators for a specific region with caching.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
region: AWS region name
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
List of configuration aggregators
|
|
179
|
+
"""
|
|
180
|
+
# Check cache first
|
|
181
|
+
cache_key = f"{region}:{self.session.region_name}"
|
|
182
|
+
if cache_key in self.__class__._config_organization_aggregator:
|
|
183
|
+
logger.debug(f"Using cached configuration aggregators for {region}")
|
|
184
|
+
return self.__class__._config_organization_aggregator[cache_key]
|
|
185
|
+
|
|
186
|
+
# Get client for the region
|
|
187
|
+
client = self.get_client(region)
|
|
188
|
+
if not client:
|
|
189
|
+
logger.warning(f"No Config client available for region {region}")
|
|
190
|
+
return []
|
|
191
|
+
|
|
192
|
+
# Get configuration aggregators from client
|
|
193
|
+
aggregators = client.describe_configuration_aggregators()
|
|
194
|
+
|
|
195
|
+
# Cache the results
|
|
196
|
+
self.__class__._config_organization_aggregator[cache_key] = aggregators
|
|
197
|
+
logger.debug(f"Cached {len(aggregators)} configuration aggregators for {region}")
|
|
198
|
+
|
|
199
|
+
return aggregators
|
|
200
|
+
|
|
201
|
+
def get_delegated_administrators(self, service_principal=None) -> List[Dict[str, Any]]:
|
|
202
|
+
"""
|
|
203
|
+
Get Config delegated administrators with caching.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
service_principal: Optional specific service principal to check
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
List of delegated administrators
|
|
210
|
+
"""
|
|
211
|
+
if not self.regions:
|
|
212
|
+
logger.warning("No regions specified")
|
|
213
|
+
return []
|
|
214
|
+
|
|
215
|
+
account_id = self.account_id
|
|
216
|
+
if not account_id:
|
|
217
|
+
logger.warning("Could not determine account ID")
|
|
218
|
+
return []
|
|
219
|
+
|
|
220
|
+
# If a specific service principal is provided, only check that one
|
|
221
|
+
service_principals = [service_principal] if service_principal else self.CONFIG_SERVICE_PRINCIPALS
|
|
222
|
+
|
|
223
|
+
all_delegated_admins = []
|
|
224
|
+
|
|
225
|
+
for sp in service_principals:
|
|
226
|
+
# Check cache first
|
|
227
|
+
cache_key = f"{account_id}:{self.session.region_name}:{sp}"
|
|
228
|
+
if cache_key in self.__class__._config_delegated_admin_cache:
|
|
229
|
+
logger.debug(f"Using cached delegated administrators for {cache_key}")
|
|
230
|
+
admins = self.__class__._config_delegated_admin_cache[cache_key]
|
|
231
|
+
all_delegated_admins.extend(admins)
|
|
232
|
+
continue
|
|
233
|
+
|
|
234
|
+
# Use any region to get delegated administrators
|
|
235
|
+
client = self.get_client(self.regions[0])
|
|
236
|
+
if not client:
|
|
237
|
+
logger.warning("No Config client available")
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
# Get delegated administrators from client
|
|
241
|
+
delegated_admins = client.list_delegated_administrators(sp)
|
|
242
|
+
|
|
243
|
+
# Cache the results
|
|
244
|
+
self.__class__._config_delegated_admin_cache[cache_key] = delegated_admins
|
|
245
|
+
logger.debug(f"Cached {len(delegated_admins)} delegated administrators for {cache_key}")
|
|
246
|
+
|
|
247
|
+
all_delegated_admins.extend(delegated_admins)
|
|
248
|
+
|
|
249
|
+
return all_delegated_admins
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Config checks package."""
|