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,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Check if CloudWatch alarms exist for Shield Advanced protected CloudFront and Route53 resources.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Dict, List, Any
|
|
5
|
+
from sraverify.services.shield.base import ShieldCheck
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SRA_SHIELD_13(ShieldCheck):
|
|
9
|
+
"""Check if CloudWatch alarms exist for Shield Advanced protected CloudFront and Route53 resources."""
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
"""Initialize Shield Advanced CloudWatch alarms check."""
|
|
13
|
+
super().__init__()
|
|
14
|
+
self.check_id = "SRA-SHIELD-13"
|
|
15
|
+
self.check_name = "CloudWatch alarms exist for Shield Advanced protected CloudFront and Route53 resources"
|
|
16
|
+
self.description = ("This check verifies that CloudWatch alarms are configured for "
|
|
17
|
+
"Shield Advanced protected CloudFront distributions and Route53 hosted zones "
|
|
18
|
+
"to monitor DDoS detection metrics (DDoSDetected).")
|
|
19
|
+
self.severity = "MEDIUM"
|
|
20
|
+
self.check_logic = ("List Shield protections for CloudFront and Route53 resources, "
|
|
21
|
+
"then check if CloudWatch alarms exist for DDoSDetected metric.")
|
|
22
|
+
|
|
23
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
24
|
+
"""
|
|
25
|
+
Execute the check.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
List of findings
|
|
29
|
+
"""
|
|
30
|
+
# Shield metrics for CloudFront and Route53 are reported in us-east-1
|
|
31
|
+
region = "us-east-1"
|
|
32
|
+
protections = self.list_protections(region)
|
|
33
|
+
|
|
34
|
+
if "Error" in protections:
|
|
35
|
+
error_code = protections["Error"].get("Code", "")
|
|
36
|
+
if error_code == "ResourceNotFoundException":
|
|
37
|
+
self.findings.append(self.create_finding(
|
|
38
|
+
status="FAIL",
|
|
39
|
+
region=region,
|
|
40
|
+
resource_id=None,
|
|
41
|
+
actual_value="Shield Advanced subscription not found",
|
|
42
|
+
remediation="Enable Shield Advanced subscription to protect resources"
|
|
43
|
+
))
|
|
44
|
+
else:
|
|
45
|
+
self.findings.append(self.create_finding(
|
|
46
|
+
status="ERROR",
|
|
47
|
+
region=region,
|
|
48
|
+
resource_id=None,
|
|
49
|
+
actual_value=protections["Error"].get("Message", "Unknown error"),
|
|
50
|
+
remediation="Check IAM permissions for Shield API access"
|
|
51
|
+
))
|
|
52
|
+
elif protections.get("Protections"):
|
|
53
|
+
# Filter for CloudFront and Route53 resources
|
|
54
|
+
cf_r53_protections = [
|
|
55
|
+
p for p in protections["Protections"]
|
|
56
|
+
if ("cloudfront" in p.get("ResourceArn", "").lower() or
|
|
57
|
+
"route53" in p.get("ResourceArn", "").lower())
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
if not cf_r53_protections:
|
|
61
|
+
self.findings.append(self.create_finding(
|
|
62
|
+
status="PASS",
|
|
63
|
+
region=region,
|
|
64
|
+
resource_id="shield:cloudwatch-alarms",
|
|
65
|
+
actual_value="No CloudFront or Route53 protected resources found",
|
|
66
|
+
remediation=""
|
|
67
|
+
))
|
|
68
|
+
return self.findings
|
|
69
|
+
|
|
70
|
+
# Check each resource for CloudWatch alarms
|
|
71
|
+
for protection in cf_r53_protections:
|
|
72
|
+
resource_arn = protection.get("ResourceArn", "")
|
|
73
|
+
protection_name = protection.get("Name", "Unknown")
|
|
74
|
+
|
|
75
|
+
# Check for DDoSDetected alarm
|
|
76
|
+
alarms = self.get_cloudwatch_alarms_for_resource(region, resource_arn)
|
|
77
|
+
|
|
78
|
+
if "Error" in alarms:
|
|
79
|
+
self.findings.append(self.create_finding(
|
|
80
|
+
status="ERROR",
|
|
81
|
+
region=region,
|
|
82
|
+
resource_id=resource_arn,
|
|
83
|
+
actual_value=alarms["Error"].get("Message", "Unknown error"),
|
|
84
|
+
remediation="Check IAM permissions for CloudWatch API access"
|
|
85
|
+
))
|
|
86
|
+
elif alarms.get("DDoSDetectedAlarms"):
|
|
87
|
+
alarm_names = [alarm["AlarmName"] for alarm in alarms["DDoSDetectedAlarms"]]
|
|
88
|
+
self.findings.append(self.create_finding(
|
|
89
|
+
status="PASS",
|
|
90
|
+
region=region,
|
|
91
|
+
resource_id=resource_arn,
|
|
92
|
+
actual_value=f"DDoSDetected alarms configured: {', '.join(alarm_names)}",
|
|
93
|
+
remediation=""
|
|
94
|
+
))
|
|
95
|
+
else:
|
|
96
|
+
self.findings.append(self.create_finding(
|
|
97
|
+
status="FAIL",
|
|
98
|
+
region=region,
|
|
99
|
+
resource_id=resource_arn,
|
|
100
|
+
actual_value="No DDoSDetected CloudWatch alarms configured",
|
|
101
|
+
remediation="Create CloudWatch alarm for DDoSDetected metric to monitor DDoS events"
|
|
102
|
+
))
|
|
103
|
+
else:
|
|
104
|
+
self.findings.append(self.create_finding(
|
|
105
|
+
status="PASS",
|
|
106
|
+
region=region,
|
|
107
|
+
resource_id=None,
|
|
108
|
+
actual_value="No Shield Advanced protections found",
|
|
109
|
+
remediation=""
|
|
110
|
+
))
|
|
111
|
+
|
|
112
|
+
return self.findings
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Check if Shield Advanced protected resources have automatic application layer DDoS mitigation enabled.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Dict, List, Any
|
|
5
|
+
from sraverify.services.shield.base import ShieldCheck
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SRA_SHIELD_14(ShieldCheck):
|
|
9
|
+
"""Check if Shield Advanced protected resources have automatic application layer DDoS mitigation enabled."""
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
"""Initialize Shield Advanced automatic mitigation check."""
|
|
13
|
+
super().__init__()
|
|
14
|
+
self.check_id = "SRA-SHIELD-14"
|
|
15
|
+
self.check_name = "Shield Advanced protected resources have automatic application layer DDoS mitigation enabled"
|
|
16
|
+
self.description = ("This check verifies that Shield Advanced protected application layer resources "
|
|
17
|
+
"(CloudFront distributions and Application Load Balancers) have automatic "
|
|
18
|
+
"application layer DDoS mitigation enabled with Block action for effective protection.")
|
|
19
|
+
self.severity = "HIGH"
|
|
20
|
+
self.check_logic = ("List Shield protections and check ApplicationLayerAutomaticResponseConfiguration "
|
|
21
|
+
"status and action for application layer resources.")
|
|
22
|
+
|
|
23
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
24
|
+
"""
|
|
25
|
+
Execute the check.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
List of findings
|
|
29
|
+
"""
|
|
30
|
+
# Shield is a global service, check only in us-east-1
|
|
31
|
+
region = "us-east-1"
|
|
32
|
+
protections = self.list_protections(region)
|
|
33
|
+
|
|
34
|
+
if "Error" in protections:
|
|
35
|
+
error_code = protections["Error"].get("Code", "")
|
|
36
|
+
if error_code == "ResourceNotFoundException":
|
|
37
|
+
self.findings.append(self.create_finding(
|
|
38
|
+
status="FAIL",
|
|
39
|
+
region=region,
|
|
40
|
+
resource_id=None,
|
|
41
|
+
actual_value="Shield Advanced subscription not found",
|
|
42
|
+
remediation="Enable Shield Advanced subscription to protect resources"
|
|
43
|
+
))
|
|
44
|
+
else:
|
|
45
|
+
self.findings.append(self.create_finding(
|
|
46
|
+
status="ERROR",
|
|
47
|
+
region=region,
|
|
48
|
+
resource_id=None,
|
|
49
|
+
actual_value=protections["Error"].get("Message", "Unknown error"),
|
|
50
|
+
remediation="Check IAM permissions for Shield API access"
|
|
51
|
+
))
|
|
52
|
+
elif protections.get("Protections"):
|
|
53
|
+
# Filter for application layer resources (CloudFront and ALB)
|
|
54
|
+
app_layer_protections = [
|
|
55
|
+
p for p in protections["Protections"]
|
|
56
|
+
if ("cloudfront" in p.get("ResourceArn", "").lower() or
|
|
57
|
+
"elasticloadbalancing" in p.get("ResourceArn", "").lower())
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
if not app_layer_protections:
|
|
61
|
+
self.findings.append(self.create_finding(
|
|
62
|
+
status="PASS",
|
|
63
|
+
region=region,
|
|
64
|
+
resource_id="shield:automatic-mitigation",
|
|
65
|
+
actual_value="No application layer protected resources found",
|
|
66
|
+
remediation=""
|
|
67
|
+
))
|
|
68
|
+
return self.findings
|
|
69
|
+
|
|
70
|
+
# Check each application layer resource for automatic mitigation
|
|
71
|
+
for protection in app_layer_protections:
|
|
72
|
+
resource_arn = protection.get("ResourceArn", "")
|
|
73
|
+
|
|
74
|
+
# Check if ApplicationLayerAutomaticResponseConfiguration exists in the protection
|
|
75
|
+
auto_response_config = protection.get("ApplicationLayerAutomaticResponseConfiguration")
|
|
76
|
+
|
|
77
|
+
if auto_response_config and auto_response_config.get("Status") == "ENABLED":
|
|
78
|
+
if "Block" in auto_response_config.get("Action", {}):
|
|
79
|
+
self.findings.append(self.create_finding(
|
|
80
|
+
status="PASS",
|
|
81
|
+
region=region,
|
|
82
|
+
resource_id=resource_arn,
|
|
83
|
+
actual_value="Automatic mitigation enabled with Block action",
|
|
84
|
+
remediation=""
|
|
85
|
+
))
|
|
86
|
+
else:
|
|
87
|
+
self.findings.append(self.create_finding(
|
|
88
|
+
status="FAIL",
|
|
89
|
+
region=region,
|
|
90
|
+
resource_id=resource_arn,
|
|
91
|
+
actual_value="Automatic mitigation enabled but using Count action",
|
|
92
|
+
remediation="Change automatic mitigation action from Count to Block for effective protection"
|
|
93
|
+
))
|
|
94
|
+
else:
|
|
95
|
+
self.findings.append(self.create_finding(
|
|
96
|
+
status="FAIL",
|
|
97
|
+
region=region,
|
|
98
|
+
resource_id=resource_arn,
|
|
99
|
+
actual_value="Automatic application layer DDoS mitigation not enabled",
|
|
100
|
+
remediation="Enable automatic application layer DDoS mitigation with Block action in Shield Advanced console"
|
|
101
|
+
))
|
|
102
|
+
else:
|
|
103
|
+
self.findings.append(self.create_finding(
|
|
104
|
+
status="PASS",
|
|
105
|
+
region=region,
|
|
106
|
+
resource_id=None,
|
|
107
|
+
actual_value="No Shield Advanced protections found",
|
|
108
|
+
remediation=""
|
|
109
|
+
))
|
|
110
|
+
|
|
111
|
+
return self.findings
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shield client for interacting with AWS Shield service.
|
|
3
|
+
"""
|
|
4
|
+
from typing import Dict, Optional, Any
|
|
5
|
+
import boto3
|
|
6
|
+
from botocore.exceptions import ClientError
|
|
7
|
+
from sraverify.core.logging import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ShieldClient:
|
|
11
|
+
"""Client for interacting with AWS Shield service."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, region: str, session: Optional[boto3.Session] = None):
|
|
14
|
+
"""
|
|
15
|
+
Initialize Shield 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('shield', region_name=region)
|
|
24
|
+
|
|
25
|
+
def get_subscription_state(self) -> Dict[str, Any]:
|
|
26
|
+
"""
|
|
27
|
+
Get Shield Advanced subscription state.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Dictionary containing subscription details or error information
|
|
31
|
+
"""
|
|
32
|
+
try:
|
|
33
|
+
return self.client.describe_subscription()
|
|
34
|
+
except ClientError as e:
|
|
35
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
36
|
+
error_message = str(e)
|
|
37
|
+
logger.debug(f"Error getting Shield subscription in {self.region}: {error_message}")
|
|
38
|
+
return {
|
|
39
|
+
"Error": {
|
|
40
|
+
"Code": error_code,
|
|
41
|
+
"Message": error_message
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
def get_subscription_status(self) -> Dict[str, Any]:
|
|
46
|
+
"""
|
|
47
|
+
Get Shield Advanced subscription status (ACTIVE/INACTIVE).
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Dictionary containing subscription state or error information
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
return self.client.get_subscription_state()
|
|
54
|
+
except ClientError as e:
|
|
55
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
56
|
+
error_message = str(e)
|
|
57
|
+
logger.debug(f"Error getting Shield subscription state in {self.region}: {error_message}")
|
|
58
|
+
return {
|
|
59
|
+
"Error": {
|
|
60
|
+
"Code": error_code,
|
|
61
|
+
"Message": error_message
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
def list_protections(self, resource_type: Optional[str] = None) -> Dict[str, Any]:
|
|
66
|
+
"""
|
|
67
|
+
List Shield Advanced protections.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
resource_type: Optional resource type filter
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Dictionary containing protections list or error information
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
params = {}
|
|
77
|
+
if resource_type:
|
|
78
|
+
params['InclusionFilters'] = {'ResourceTypes': [resource_type]}
|
|
79
|
+
|
|
80
|
+
return self.client.list_protections(**params)
|
|
81
|
+
except ClientError as e:
|
|
82
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
83
|
+
error_message = str(e)
|
|
84
|
+
logger.debug(f"Error listing Shield protections in {self.region}: {error_message}")
|
|
85
|
+
return {
|
|
86
|
+
"Error": {
|
|
87
|
+
"Code": error_code,
|
|
88
|
+
"Message": error_message
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
def describe_drt_access(self) -> Dict[str, Any]:
|
|
93
|
+
"""
|
|
94
|
+
Describe Shield Response Team (SRT) access configuration.
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Dictionary containing DRT access details or error information
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
return self.client.describe_drt_access()
|
|
101
|
+
except ClientError as e:
|
|
102
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
103
|
+
error_message = str(e)
|
|
104
|
+
logger.debug(f"Error describing DRT access in {self.region}: {error_message}")
|
|
105
|
+
return {
|
|
106
|
+
"Error": {
|
|
107
|
+
"Code": error_code,
|
|
108
|
+
"Message": error_message
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
def get_lambda_function(self, function_name: str) -> Dict[str, Any]:
|
|
113
|
+
"""
|
|
114
|
+
Get Lambda function details.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
function_name: Name of the Lambda function
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
Dictionary containing function details or error information
|
|
121
|
+
"""
|
|
122
|
+
try:
|
|
123
|
+
lambda_client = self.session.client('lambda', region_name=self.region)
|
|
124
|
+
return lambda_client.get_function(FunctionName=function_name)
|
|
125
|
+
except ClientError as e:
|
|
126
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
127
|
+
error_message = str(e)
|
|
128
|
+
logger.debug(f"Error getting Lambda function {function_name} in {self.region}: {error_message}")
|
|
129
|
+
return {
|
|
130
|
+
"Error": {
|
|
131
|
+
"Code": error_code,
|
|
132
|
+
"Message": error_message
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
def get_web_acl_for_resource(self, resource_arn: str) -> Dict[str, Any]:
|
|
137
|
+
"""
|
|
138
|
+
Get WAF web ACL associated with a resource.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
resource_arn: ARN of the resource
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Dictionary containing web ACL details or error information
|
|
145
|
+
"""
|
|
146
|
+
try:
|
|
147
|
+
# For CloudFront distributions, use CloudFront API
|
|
148
|
+
if "cloudfront" in resource_arn.lower():
|
|
149
|
+
# Extract distribution ID from ARN: arn:aws:cloudfront::account:distribution/ID
|
|
150
|
+
distribution_id = resource_arn.split("/")[-1]
|
|
151
|
+
cloudfront_client = self.session.client('cloudfront', region_name='us-east-1')
|
|
152
|
+
response = cloudfront_client.get_distribution_config(Id=distribution_id)
|
|
153
|
+
web_acl_id = response.get('DistributionConfig', {}).get('WebACLId', '')
|
|
154
|
+
|
|
155
|
+
if web_acl_id:
|
|
156
|
+
return {"WebACL": {"Id": web_acl_id, "Name": f"WebACL-{web_acl_id}"}}
|
|
157
|
+
else:
|
|
158
|
+
return {"Error": {"Code": "WAFNonexistentItemException", "Message": "No web ACL associated"}}
|
|
159
|
+
else:
|
|
160
|
+
# For other resources, use WAFv2 API
|
|
161
|
+
wafv2_client = self.session.client('wafv2', region_name=self.region)
|
|
162
|
+
return wafv2_client.get_web_acl_for_resource(ResourceArn=resource_arn)
|
|
163
|
+
except ClientError as e:
|
|
164
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
165
|
+
error_message = str(e)
|
|
166
|
+
logger.debug(f"Error getting web ACL for resource {resource_arn} in {self.region}: {error_message}")
|
|
167
|
+
return {
|
|
168
|
+
"Error": {
|
|
169
|
+
"Code": error_code,
|
|
170
|
+
"Message": error_message
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
def get_cloudwatch_alarms_for_resource(self, resource_arn: str) -> Dict[str, Any]:
|
|
175
|
+
"""
|
|
176
|
+
Get CloudWatch alarms for Shield Advanced DDoS metrics for a resource.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
resource_arn: ARN of the resource
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Dictionary containing alarm details or error information
|
|
183
|
+
"""
|
|
184
|
+
try:
|
|
185
|
+
cloudwatch_client = self.session.client('cloudwatch', region_name=self.region)
|
|
186
|
+
|
|
187
|
+
# Look for alarms on DDoSDetected metric for this resource
|
|
188
|
+
response = cloudwatch_client.describe_alarms_for_metric(
|
|
189
|
+
MetricName='DDoSDetected',
|
|
190
|
+
Namespace='AWS/DDoSProtection',
|
|
191
|
+
Dimensions=[
|
|
192
|
+
{
|
|
193
|
+
'Name': 'ResourceArn',
|
|
194
|
+
'Value': resource_arn
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
ddos_alarms = response.get('MetricAlarms', [])
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
"DDoSDetectedAlarms": ddos_alarms
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
except ClientError as e:
|
|
206
|
+
error_code = e.response.get('Error', {}).get('Code', '')
|
|
207
|
+
error_message = str(e)
|
|
208
|
+
logger.debug(f"Error getting CloudWatch alarms for resource {resource_arn} in {self.region}: {error_message}")
|
|
209
|
+
return {
|
|
210
|
+
"Error": {
|
|
211
|
+
"Code": error_code,
|
|
212
|
+
"Message": error_message
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from sraverify.services.waf.checks.sra_waf_01 import SRA_WAF_01
|
|
2
|
+
from sraverify.services.waf.checks.sra_waf_02 import SRA_WAF_02
|
|
3
|
+
from sraverify.services.waf.checks.sra_waf_03 import SRA_WAF_03
|
|
4
|
+
from sraverify.services.waf.checks.sra_waf_04 import SRA_WAF_04
|
|
5
|
+
from sraverify.services.waf.checks.sra_waf_05 import SRA_WAF_05
|
|
6
|
+
from sraverify.services.waf.checks.sra_waf_06 import SRA_WAF_06
|
|
7
|
+
from sraverify.services.waf.checks.sra_waf_07 import SRA_WAF_07
|
|
8
|
+
from sraverify.services.waf.checks.sra_waf_08 import SRA_WAF_08
|
|
9
|
+
from sraverify.services.waf.checks.sra_waf_09 import SRA_WAF_09
|
|
10
|
+
|
|
11
|
+
CHECKS = {
|
|
12
|
+
"SRA-WAF-01": SRA_WAF_01,
|
|
13
|
+
"SRA-WAF-02": SRA_WAF_02,
|
|
14
|
+
"SRA-WAF-03": SRA_WAF_03,
|
|
15
|
+
"SRA-WAF-04": SRA_WAF_04,
|
|
16
|
+
"SRA-WAF-05": SRA_WAF_05,
|
|
17
|
+
"SRA-WAF-06": SRA_WAF_06,
|
|
18
|
+
"SRA-WAF-07": SRA_WAF_07,
|
|
19
|
+
"SRA-WAF-08": SRA_WAF_08,
|
|
20
|
+
"SRA-WAF-09": SRA_WAF_09,
|
|
21
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
from typing import Dict, Any
|
|
2
|
+
from sraverify.core.check import SecurityCheck
|
|
3
|
+
from sraverify.services.waf.client import WAFClient
|
|
4
|
+
|
|
5
|
+
class WAFCheck(SecurityCheck):
|
|
6
|
+
def __init__(self):
|
|
7
|
+
super().__init__(
|
|
8
|
+
account_type="application",
|
|
9
|
+
service="WAF",
|
|
10
|
+
resource_type="AWS::ElasticLoadBalancingV2::LoadBalancer"
|
|
11
|
+
)
|
|
12
|
+
self._distributions_cache = {}
|
|
13
|
+
self._load_balancers_cache = {}
|
|
14
|
+
self._rest_apis_cache = {}
|
|
15
|
+
self._graphql_apis_cache = {}
|
|
16
|
+
self._user_pools_cache = {}
|
|
17
|
+
self._apprunner_services_cache = {}
|
|
18
|
+
self._verified_access_instances_cache = {}
|
|
19
|
+
self._amplify_apps_cache = {}
|
|
20
|
+
self._web_acls_cache = {}
|
|
21
|
+
|
|
22
|
+
def _setup_clients(self):
|
|
23
|
+
self._clients.clear()
|
|
24
|
+
# WAF for CloudFront is global, use us-east-1
|
|
25
|
+
self._clients['us-east-1'] = WAFClient('us-east-1', session=self.session)
|
|
26
|
+
# For ALB, API Gateway, AppSync, Cognito, App Runner, Verified Access, Amplify, and Web ACLs, create clients for all regions
|
|
27
|
+
if hasattr(self, 'regions') and self.regions:
|
|
28
|
+
for region in self.regions:
|
|
29
|
+
if region not in self._clients:
|
|
30
|
+
self._clients[region] = WAFClient(region, session=self.session)
|
|
31
|
+
|
|
32
|
+
def get_distributions(self) -> Dict[str, Any]:
|
|
33
|
+
if not self._distributions_cache:
|
|
34
|
+
client = self.get_client('us-east-1')
|
|
35
|
+
if client:
|
|
36
|
+
self._distributions_cache = client.list_distributions()
|
|
37
|
+
return self._distributions_cache
|
|
38
|
+
|
|
39
|
+
def get_load_balancers(self, region: str) -> Dict[str, Any]:
|
|
40
|
+
if region not in self._load_balancers_cache:
|
|
41
|
+
client = self.get_client(region)
|
|
42
|
+
if client:
|
|
43
|
+
self._load_balancers_cache[region] = client.describe_load_balancers()
|
|
44
|
+
return self._load_balancers_cache.get(region, {})
|
|
45
|
+
|
|
46
|
+
def get_rest_apis(self, region: str) -> Dict[str, Any]:
|
|
47
|
+
if region not in self._rest_apis_cache:
|
|
48
|
+
client = self.get_client(region)
|
|
49
|
+
if client:
|
|
50
|
+
self._rest_apis_cache[region] = client.get_rest_apis()
|
|
51
|
+
return self._rest_apis_cache.get(region, {})
|
|
52
|
+
|
|
53
|
+
def get_stages(self, region: str, rest_api_id: str) -> Dict[str, Any]:
|
|
54
|
+
client = self.get_client(region)
|
|
55
|
+
if client:
|
|
56
|
+
return client.get_stages(rest_api_id)
|
|
57
|
+
return {"Error": {"Message": "No client available"}}
|
|
58
|
+
|
|
59
|
+
def get_graphql_apis(self, region: str) -> Dict[str, Any]:
|
|
60
|
+
if region not in self._graphql_apis_cache:
|
|
61
|
+
client = self.get_client(region)
|
|
62
|
+
if client:
|
|
63
|
+
self._graphql_apis_cache[region] = client.list_graphql_apis()
|
|
64
|
+
return self._graphql_apis_cache.get(region, {})
|
|
65
|
+
|
|
66
|
+
def get_user_pools(self, region: str) -> Dict[str, Any]:
|
|
67
|
+
if region not in self._user_pools_cache:
|
|
68
|
+
client = self.get_client(region)
|
|
69
|
+
if client:
|
|
70
|
+
self._user_pools_cache[region] = client.list_user_pools()
|
|
71
|
+
return self._user_pools_cache.get(region, {})
|
|
72
|
+
|
|
73
|
+
def get_apprunner_services(self, region: str) -> Dict[str, Any]:
|
|
74
|
+
if region not in self._apprunner_services_cache:
|
|
75
|
+
client = self.get_client(region)
|
|
76
|
+
if client:
|
|
77
|
+
self._apprunner_services_cache[region] = client.list_services()
|
|
78
|
+
return self._apprunner_services_cache.get(region, {})
|
|
79
|
+
|
|
80
|
+
def get_verified_access_instances(self, region: str) -> Dict[str, Any]:
|
|
81
|
+
if region not in self._verified_access_instances_cache:
|
|
82
|
+
client = self.get_client(region)
|
|
83
|
+
if client:
|
|
84
|
+
self._verified_access_instances_cache[region] = client.describe_verified_access_instances()
|
|
85
|
+
return self._verified_access_instances_cache.get(region, {})
|
|
86
|
+
|
|
87
|
+
def get_amplify_apps(self, region: str) -> Dict[str, Any]:
|
|
88
|
+
if region not in self._amplify_apps_cache:
|
|
89
|
+
client = self.get_client(region)
|
|
90
|
+
if client:
|
|
91
|
+
self._amplify_apps_cache[region] = client.list_apps()
|
|
92
|
+
return self._amplify_apps_cache.get(region, {})
|
|
93
|
+
|
|
94
|
+
def get_web_acls(self, region: str, scope: str = "REGIONAL") -> Dict[str, Any]:
|
|
95
|
+
cache_key = f"{region}_{scope}"
|
|
96
|
+
if cache_key not in self._web_acls_cache:
|
|
97
|
+
client = self.get_client(region)
|
|
98
|
+
if client:
|
|
99
|
+
self._web_acls_cache[cache_key] = client.list_web_acls(scope)
|
|
100
|
+
return self._web_acls_cache.get(cache_key, {})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# WAF security checks
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from typing import Dict, List, Any
|
|
2
|
+
from sraverify.services.waf.base import WAFCheck
|
|
3
|
+
|
|
4
|
+
class SRA_WAF_01(WAFCheck):
|
|
5
|
+
def __init__(self):
|
|
6
|
+
super().__init__()
|
|
7
|
+
self.resource_type = "AWS::CloudFront::Distribution"
|
|
8
|
+
self.check_id = "SRA-WAF-01"
|
|
9
|
+
self.check_name = "CloudFront distributions should be associated with AWS WAF"
|
|
10
|
+
self.description = "Ensures that all CloudFront distributions are protected by AWS WAF web ACLs to filter malicious traffic"
|
|
11
|
+
self.severity = "HIGH"
|
|
12
|
+
self.check_logic = "Lists all CloudFront distributions and verifies each has a WebACLId configured"
|
|
13
|
+
|
|
14
|
+
def execute(self) -> List[Dict[str, Any]]:
|
|
15
|
+
region = "us-east-1" # CloudFront is global service
|
|
16
|
+
|
|
17
|
+
distributions_response = self.get_distributions()
|
|
18
|
+
|
|
19
|
+
if "Error" in distributions_response:
|
|
20
|
+
self.findings.append(self.create_finding(
|
|
21
|
+
status="ERROR",
|
|
22
|
+
region=region,
|
|
23
|
+
resource_id=None,
|
|
24
|
+
actual_value=distributions_response["Error"].get("Message", "Unknown error"),
|
|
25
|
+
remediation="Check IAM permissions for CloudFront API access"
|
|
26
|
+
))
|
|
27
|
+
return self.findings
|
|
28
|
+
|
|
29
|
+
distribution_list = distributions_response.get("DistributionList", {})
|
|
30
|
+
distributions = distribution_list.get("Items", [])
|
|
31
|
+
|
|
32
|
+
if not distributions:
|
|
33
|
+
self.findings.append(self.create_finding(
|
|
34
|
+
status="PASS",
|
|
35
|
+
region=region,
|
|
36
|
+
resource_id="No distributions",
|
|
37
|
+
actual_value="No CloudFront distributions found",
|
|
38
|
+
remediation="No action needed"
|
|
39
|
+
))
|
|
40
|
+
return self.findings
|
|
41
|
+
|
|
42
|
+
for distribution in distributions:
|
|
43
|
+
distribution_id = distribution.get("Id")
|
|
44
|
+
web_acl_id = distribution.get("WebACLId")
|
|
45
|
+
|
|
46
|
+
if web_acl_id:
|
|
47
|
+
self.findings.append(self.create_finding(
|
|
48
|
+
status="PASS",
|
|
49
|
+
region=region,
|
|
50
|
+
resource_id=distribution_id,
|
|
51
|
+
actual_value=f"WAF Web ACL associated: {web_acl_id}",
|
|
52
|
+
remediation="No action needed"
|
|
53
|
+
))
|
|
54
|
+
else:
|
|
55
|
+
self.findings.append(self.create_finding(
|
|
56
|
+
status="FAIL",
|
|
57
|
+
region=region,
|
|
58
|
+
resource_id=distribution_id,
|
|
59
|
+
actual_value="No WAF Web ACL associated",
|
|
60
|
+
remediation="Associate a WAF Web ACL with this CloudFront distribution using the AWS Console, CLI, or API"
|
|
61
|
+
))
|
|
62
|
+
|
|
63
|
+
return self.findings
|