Merge branch 'cloudflare-pr3-bot-config-checks' into cloudflare-pr4-dns-firewall-waf

This commit is contained in:
HugoPBrito
2025-12-16 15:28:04 +01:00
6 changed files with 111 additions and 140 deletions

View File

@@ -1,35 +0,0 @@
{
"Provider": "cloudflare",
"CheckID": "zones_security_level",
"CheckTitle": "Security level is set to medium or higher for adequate threat protection",
"CheckType": [],
"ServiceName": "zones",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Zone",
"Description": "**Cloudflare zones** are assessed for **Security Level** configuration by checking if it is set to `medium`, `high`, or `under_attack` to ensure adequate **threat protection** based on visitor reputation scores.",
"Risk": "A **low security level** allows more potentially malicious traffic to reach your origin.\n- **Confidentiality**: increased exposure to credential stuffing and data exfiltration attempts\n- **Integrity**: higher risk of successful web application attacks\n- **Availability**: greater attack surface for DDoS and resource exhaustion attacks",
"RelatedUrl": "",
"AdditionalURLs": [
"https://developers.cloudflare.com/waf/tools/security-level/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Log in to the Cloudflare dashboard and select your account and domain\n2. Go to Security > Settings\n3. Scroll to Security Level\n4. Select Medium or High from the dropdown based on your threat tolerance\n5. Use Under Attack mode only during active attacks (temporarily)",
"Terraform": "```hcl\n# Set security level to medium or higher\nresource \"cloudflare_zone_settings_override\" \"security_level\" {\n zone_id = \"<ZONE_ID>\"\n settings {\n security_level = \"medium\" # Options: off, essentially_off, low, medium, high, under_attack\n }\n}\n```"
},
"Recommendation": {
"Text": "Set **Security Level** to `Medium` or `High` based on your threat tolerance.\n- **Low**: minimal protection, may allow suspicious traffic\n- **Medium**: balanced protection for most sites (recommended baseline)\n- **High**: aggressive filtering, may challenge more visitors\n- **Under Attack**: use only during active attacks, impacts user experience",
"Url": "https://hub.prowler.com/checks/cloudflare/zones_security_level"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Avoid keeping Under Attack mode enabled permanently as it presents challenges to all visitors. Use Page Rules for path-specific security levels if needed."
}

View File

@@ -0,0 +1,36 @@
{
"Provider": "cloudflare",
"CheckID": "zones_security_under_attack_disabled",
"CheckTitle": "Under Attack Mode is disabled during normal operations",
"CheckType": [],
"ServiceName": "zones",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
"ResourceType": "Zone",
"Description": "**Cloudflare zones** are assessed for **Under Attack Mode** configuration by checking if it is disabled during normal operations, as this mode performs additional security checks including an **interstitial JavaScript challenge page** that significantly impacts user experience.",
"Risk": "Keeping **Under Attack Mode** permanently enabled causes operational issues.\n- **Availability**: all visitors face a 5-second interstitial challenge page before accessing the site\n- **Accessibility**: visitors without JavaScript support cannot access the site at all\n- **User Experience**: legitimate users experience unnecessary delays and third-party analytics show degraded performance",
"RelatedUrl": "",
"AdditionalURLs": [
"https://developers.cloudflare.com/fundamentals/reference/under-attack-mode/",
"https://developers.cloudflare.com/waf/tools/security-level/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Log in to the Cloudflare dashboard and select your account and domain\n2. Go to Security > Settings\n3. Under 'Under Attack Mode', toggle it OFF\n4. Consider setting Security Level to 'High' for continued protection without the interstitial",
"Terraform": "```hcl\n# Set security level to high instead of under_attack\nresource \"cloudflare_zone_settings_override\" \"security_settings\" {\n zone_id = \"<ZONE_ID>\"\n settings {\n security_level = \"high\" # Use high instead of under_attack for normal operations\n }\n}\n```"
},
"Recommendation": {
"Text": "Disable **Under Attack Mode** when not actively under a DDoS attack.\n- Use it only as a **last resort** during active layer 7 DDoS attacks\n- For ongoing protection, use **Security Level** settings (Low, Medium, High)\n- Configure specific **WAF rules** for targeted protection without impacting all users",
"Url": "https://hub.prowler.com/checks/cloudflare/zones_security_under_attack_disabled"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Under Attack Mode is designed as a temporary measure during active attacks. The interstitial challenge page validates visitors using JavaScript, blocking automated attacks while allowing legitimate users through after a brief delay."
}

View File

@@ -2,10 +2,9 @@ from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.zones.zones_client import zones_client
class zones_security_level(Check):
class zones_security_under_attack_disabled(Check):
def execute(self) -> list[CheckReportCloudflare]:
findings = []
acceptable_levels = ["medium", "high", "under_attack"]
for zone in zones_client.zones.values():
report = CheckReportCloudflare(
@@ -13,15 +12,16 @@ class zones_security_level(Check):
resource=zone,
)
security_level = (zone.settings.security_level or "").lower()
if security_level in acceptable_levels:
report.status = "PASS"
report.status_extended = (
f"Security level is set to '{security_level}' for zone {zone.name}."
)
else:
if security_level == "under_attack":
report.status = "FAIL"
report.status_extended = (
f"Security level is set to '{security_level}' for zone {zone.name}."
f"Zone {zone.name} has Under Attack Mode enabled."
)
else:
report.status = "PASS"
report.status_extended = (
f"Zone {zone.name} does not have Under Attack Mode enabled."
)
findings.append(report)
return findings

View File

@@ -11,7 +11,7 @@ from tests.providers.cloudflare.cloudflare_fixtures import (
)
class Test_zones_security_level:
class Test_zones_security_under_attack_disabled:
def test_no_zones(self):
zones_client = mock.MagicMock
zones_client.zones = {}
@@ -22,18 +22,57 @@ class Test_zones_security_level:
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level.zones_client",
"prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled.zones_client",
new=zones_client,
),
):
from prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level import (
zones_security_level,
from prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled import (
zones_security_under_attack_disabled,
)
check = zones_security_level()
check = zones_security_under_attack_disabled()
result = check.execute()
assert len(result) == 0
def test_zone_under_attack_mode_enabled(self):
zones_client = mock.MagicMock
zones_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
security_level="under_attack",
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled.zones_client",
new=zones_client,
),
):
from prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled import (
zones_security_under_attack_disabled,
)
check = zones_security_under_attack_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].resource_id == ZONE_ID
assert result[0].resource_name == ZONE_NAME
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"Zone {ZONE_NAME} has Under Attack Mode enabled."
)
def test_zone_security_level_high(self):
zones_client = mock.MagicMock
zones_client.zones = {
@@ -54,21 +93,22 @@ class Test_zones_security_level:
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level.zones_client",
"prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled.zones_client",
new=zones_client,
),
):
from prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level import (
zones_security_level,
from prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled import (
zones_security_under_attack_disabled,
)
check = zones_security_level()
check = zones_security_under_attack_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].resource_id == ZONE_ID
assert result[0].resource_name == ZONE_NAME
assert result[0].status == "PASS"
assert "high" in result[0].status_extended
assert (
result[0].status_extended
== f"Zone {ZONE_NAME} does not have Under Attack Mode enabled."
)
def test_zone_security_level_medium(self):
zones_client = mock.MagicMock
@@ -90,53 +130,18 @@ class Test_zones_security_level:
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level.zones_client",
"prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled.zones_client",
new=zones_client,
),
):
from prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level import (
zones_security_level,
from prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled import (
zones_security_under_attack_disabled,
)
check = zones_security_level()
check = zones_security_under_attack_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "medium" in result[0].status_extended
def test_zone_security_level_under_attack(self):
zones_client = mock.MagicMock
zones_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
security_level="under_attack",
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level.zones_client",
new=zones_client,
),
):
from prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level import (
zones_security_level,
)
check = zones_security_level()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "under_attack" in result[0].status_extended
def test_zone_security_level_low(self):
zones_client = mock.MagicMock
@@ -158,53 +163,18 @@ class Test_zones_security_level:
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level.zones_client",
"prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled.zones_client",
new=zones_client,
),
):
from prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level import (
zones_security_level,
from prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled import (
zones_security_under_attack_disabled,
)
check = zones_security_level()
check = zones_security_under_attack_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "low" in result[0].status_extended
def test_zone_security_level_essentially_off(self):
zones_client = mock.MagicMock
zones_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
security_level="essentially_off",
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level.zones_client",
new=zones_client,
),
):
from prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level import (
zones_security_level,
)
check = zones_security_level()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "essentially_off" in result[0].status_extended
assert result[0].status == "PASS"
def test_zone_security_level_none(self):
zones_client = mock.MagicMock
@@ -226,15 +196,15 @@ class Test_zones_security_level:
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level.zones_client",
"prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled.zones_client",
new=zones_client,
),
):
from prowler.providers.cloudflare.services.zones.zones_security_level.zones_security_level import (
zones_security_level,
from prowler.providers.cloudflare.services.zones.zones_security_under_attack_disabled.zones_security_under_attack_disabled import (
zones_security_under_attack_disabled,
)
check = zones_security_level()
check = zones_security_under_attack_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert result[0].status == "PASS"