diff --git a/prowler/providers/cloudflare/services/zones/zones_security_level/zones_security_level.metadata.json b/prowler/providers/cloudflare/services/zones/zones_security_level/zones_security_level.metadata.json deleted file mode 100644 index 0fefbd5de8..0000000000 --- a/prowler/providers/cloudflare/services/zones/zones_security_level/zones_security_level.metadata.json +++ /dev/null @@ -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 = \"\"\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." -} diff --git a/prowler/providers/cloudflare/services/zones/zones_security_level/__init__.py b/prowler/providers/cloudflare/services/zones/zones_security_under_attack_disabled/__init__.py similarity index 100% rename from prowler/providers/cloudflare/services/zones/zones_security_level/__init__.py rename to prowler/providers/cloudflare/services/zones/zones_security_under_attack_disabled/__init__.py diff --git a/prowler/providers/cloudflare/services/zones/zones_security_under_attack_disabled/zones_security_under_attack_disabled.metadata.json b/prowler/providers/cloudflare/services/zones/zones_security_under_attack_disabled/zones_security_under_attack_disabled.metadata.json new file mode 100644 index 0000000000..0db13dedbb --- /dev/null +++ b/prowler/providers/cloudflare/services/zones/zones_security_under_attack_disabled/zones_security_under_attack_disabled.metadata.json @@ -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 = \"\"\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." +} diff --git a/prowler/providers/cloudflare/services/zones/zones_security_level/zones_security_level.py b/prowler/providers/cloudflare/services/zones/zones_security_under_attack_disabled/zones_security_under_attack_disabled.py similarity index 69% rename from prowler/providers/cloudflare/services/zones/zones_security_level/zones_security_level.py rename to prowler/providers/cloudflare/services/zones/zones_security_under_attack_disabled/zones_security_under_attack_disabled.py index a32b84995d..d64f312f1c 100644 --- a/prowler/providers/cloudflare/services/zones/zones_security_level/zones_security_level.py +++ b/prowler/providers/cloudflare/services/zones/zones_security_under_attack_disabled/zones_security_under_attack_disabled.py @@ -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 diff --git a/tests/providers/cloudflare/services/zones/zones_security_under_attack_disabled/__init__.py b/tests/providers/cloudflare/services/zones/zones_security_under_attack_disabled/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/providers/cloudflare/services/zones/zones_security_level/zones_security_level_test.py b/tests/providers/cloudflare/services/zones/zones_security_under_attack_disabled/zones_security_under_attack_disabled_test.py similarity index 68% rename from tests/providers/cloudflare/services/zones/zones_security_level/zones_security_level_test.py rename to tests/providers/cloudflare/services/zones/zones_security_under_attack_disabled/zones_security_under_attack_disabled_test.py index d128b40262..faf98ad9e7 100644 --- a/tests/providers/cloudflare/services/zones/zones_security_level/zones_security_level_test.py +++ b/tests/providers/cloudflare/services/zones/zones_security_under_attack_disabled/zones_security_under_attack_disabled_test.py @@ -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"