feat(cloudflare): Add bot protection and configuration checks for zones (#9425)

Co-authored-by: Andoni Alonso <14891798+andoniaf@users.noreply.github.com>
This commit is contained in:
Hugo Pereira Brito
2026-01-16 12:06:52 +01:00
committed by GitHub
parent ec4eb70539
commit aa24034ca7
38 changed files with 2165 additions and 1 deletions

View File

@@ -23,6 +23,7 @@ All notable changes to the **Prowler SDK** are documented in this file.
- CIS 1.12 compliance framework for Kubernetes [(#9778)](https://github.com/prowler-cloud/prowler/pull/9778) - CIS 1.12 compliance framework for Kubernetes [(#9778)](https://github.com/prowler-cloud/prowler/pull/9778)
- CIS 6.0 for M365 provider [(#9779)](https://github.com/prowler-cloud/prowler/pull/9779) - CIS 6.0 for M365 provider [(#9779)](https://github.com/prowler-cloud/prowler/pull/9779)
- CIS 5.0 compliance framework for the Azure provider [(#9777)](https://github.com/prowler-cloud/prowler/pull/9777) - CIS 5.0 compliance framework for the Azure provider [(#9777)](https://github.com/prowler-cloud/prowler/pull/9777)
- `Cloudflare` Bot protection, WAF, Privacy, Anti-Scraping and Zone configuration checks [(#9425)](https://github.com/prowler-cloud/prowler/pull/9425)
### Changed ### Changed
- Update AWS Step Functions service metadata to new format [(#9432)](https://github.com/prowler-cloud/prowler/pull/9432) - Update AWS Step Functions service metadata to new format [(#9432)](https://github.com/prowler-cloud/prowler/pull/9432)

View File

@@ -0,0 +1,36 @@
{
"Provider": "cloudflare",
"CheckID": "zone_always_online_disabled",
"CheckTitle": "Always Online is disabled",
"CheckType": [],
"ServiceName": "zone",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Zone",
"ResourceGroup": "network",
"Description": "**Cloudflare zones** are assessed for **Always Online** configuration by checking if it is disabled to prevent serving **stale cached content** when the origin server is unavailable, which could expose outdated or sensitive information.",
"Risk": "With **Always Online** enabled, Cloudflare serves cached pages when the origin is unavailable.\n- **Confidentiality**: stale cache may expose sensitive information that was subsequently removed\n- **Integrity**: outdated content may contain incorrect or superseded information\n- **Availability**: reliance on cached content masks origin failures requiring attention",
"RelatedUrl": "",
"AdditionalURLs": [
"https://developers.cloudflare.com/cache/how-to/always-online/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Log in to the Cloudflare dashboard and select your account and domain\n2. Go to Caching > Configuration\n3. Scroll to Always Online\n4. Toggle the setting to Off\n5. Implement proper high availability through redundant origins or load balancing",
"Terraform": "```hcl\n# Disable Always Online to prevent serving stale cached content\nresource \"cloudflare_zone_settings_override\" \"always_online\" {\n zone_id = \"<ZONE_ID>\"\n settings {\n always_online = \"off\" # Critical: prevents serving potentially stale or sensitive cached content\n }\n}\n```"
},
"Recommendation": {
"Text": "Disable **Always Online** and implement proper high availability solutions.\n- Use redundant origins or load balancing for genuine high availability\n- Stale cached content may contain outdated security information\n- Origin failures should be detected and addressed, not masked\n- Consider Cloudflare Workers for custom failover logic if needed",
"Url": "https://hub.prowler.com/checks/cloudflare/zone_always_online_disabled"
}
},
"Categories": [
"resilience"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Always Online is a legacy feature that serves cached copies of pages when the origin is unreachable. Modern high availability should use redundant origins or failover configurations."
}

View File

@@ -0,0 +1,45 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.zone.zone_client import zone_client
class zone_always_online_disabled(Check):
"""Ensure that Always Online is disabled for Cloudflare zones.
Always Online serves stale cached content when the origin server is unavailable.
While this maintains availability, it can expose outdated or potentially sensitive
information. For security-sensitive applications, it is recommended to disable
this feature to ensure users always receive current, accurate content or an
appropriate error message when the origin is down.
"""
def execute(self) -> list[CheckReportCloudflare]:
"""Execute the Always Online disabled check.
Iterates through all Cloudflare zones and verifies that Always Online
is disabled. When enabled, this feature may serve stale cached content
that could contain outdated or sensitive information.
Returns:
A list of CheckReportCloudflare objects with PASS status if Always
Online is disabled, or FAIL status if it is enabled for the zone.
"""
findings = []
for zone in zone_client.zones.values():
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
always_online = (zone.settings.always_online or "").lower()
if always_online == "off":
report.status = "PASS"
report.status_extended = (
f"Always Online is disabled for zone {zone.name}."
)
else:
report.status = "FAIL"
report.status_extended = (
f"Always Online is enabled for zone {zone.name}."
)
findings.append(report)
return findings

View File

@@ -0,0 +1,36 @@
{
"Provider": "cloudflare",
"CheckID": "zone_bot_fight_mode_enabled",
"CheckTitle": "Bot Fight Mode is enabled",
"CheckType": [],
"ServiceName": "zone",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Zone",
"ResourceGroup": "network",
"Description": "**Cloudflare zones** are assessed for **Bot Fight Mode** configuration by checking if it is enabled to detect and mitigate **automated bot traffic** targeting the zone through browser integrity checks.",
"Risk": "Without **Bot Fight Mode**, zones are vulnerable to automated attacks.\n- **Confidentiality**: web scraping bots can harvest sensitive data from your site\n- **Integrity**: credential stuffing attacks can compromise user accounts\n- **Availability**: bot traffic can overwhelm resources causing service degradation",
"RelatedUrl": "",
"AdditionalURLs": [
"https://developers.cloudflare.com/bots/get-started/free/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Log in to the Cloudflare dashboard and select your account and domain\n2. Go to Security > Bots\n3. Enable Bot Fight Mode\n4. Monitor bot analytics to fine-tune protection\n5. Consider combining with rate limiting for comprehensive protection",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable **Bot Fight Mode** as part of a layered bot management strategy.\n- Detects and challenges automated bot traffic\n- Protects against web scraping and credential stuffing\n- Combine with rate limiting and WAF rules for comprehensive protection\n- Monitor bot analytics to understand traffic patterns",
"Url": "https://hub.prowler.com/checks/cloudflare/zone_bot_fight_mode_enabled"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Bot Fight Mode is a free feature that uses browser integrity checks to detect and challenge automated traffic. For more advanced bot management, consider Cloudflare's paid Bot Management product."
}

View File

@@ -0,0 +1,42 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.zone.zone_client import zone_client
class zone_bot_fight_mode_enabled(Check):
"""Ensure that Bot Fight Mode is enabled for Cloudflare zones.
Bot Fight Mode is a free Cloudflare feature that detects and mitigates automated
bot traffic. It uses JavaScript challenges and behavioral analysis to identify
bots and block malicious automated traffic, protecting against scraping, spam,
credential stuffing, and other automated attacks.
"""
def execute(self) -> list[CheckReportCloudflare]:
"""Execute the Bot Fight Mode enabled check.
Iterates through all Cloudflare zones and verifies that Bot Fight Mode
is enabled via the Bot Management API. This feature helps identify and
block malicious bot traffic.
Returns:
A list of CheckReportCloudflare objects with PASS status if Bot Fight
Mode is enabled, or FAIL status if it is disabled for the zone.
"""
findings = []
for zone in zone_client.zones.values():
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
if zone.settings.bot_fight_mode_enabled:
report.status = "PASS"
report.status_extended = (
f"Bot Fight Mode is enabled for zone {zone.name}."
)
else:
report.status = "FAIL"
report.status_extended = (
f"Bot Fight Mode is not enabled for zone {zone.name}."
)
findings.append(report)
return findings

View File

@@ -0,0 +1,36 @@
{
"Provider": "cloudflare",
"CheckID": "zone_browser_integrity_check_enabled",
"CheckTitle": "Cloudflare Zone Browser Integrity Check Is Enabled",
"CheckType": [],
"ServiceName": "zone",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
"ResourceType": "Zone",
"ResourceGroup": "network",
"Description": "**Cloudflare zones** are assessed for **Browser Integrity Check** configuration by verifying that HTTP headers are analyzed to identify requests from bots or clients with missing/invalid browser signatures.",
"Risk": "Without **Browser Integrity Check**, malformed or suspicious requests reach the origin.\n- **Confidentiality**: basic bots can access and scrape content without challenge\n- **Integrity**: requests with invalid headers may exploit application vulnerabilities\n- **Availability**: automated traffic without browser signatures consumes resources",
"RelatedUrl": "",
"AdditionalURLs": [
"https://developers.cloudflare.com/waf/tools/browser-integrity-check/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Log in to the Cloudflare dashboard and select your account and domain\n2. Go to Security > Settings\n3. Enable Browser Integrity Check\n4. This feature is enabled by default on most Cloudflare plans",
"Terraform": "```hcl\n# Enable Browser Integrity Check\nresource \"cloudflare_zone_settings_override\" \"browser_check\" {\n zone_id = \"<ZONE_ID>\"\n settings {\n browser_check = \"on\"\n }\n}\n```"
},
"Recommendation": {
"Text": "Enable **Browser Integrity Check** to filter basic bot traffic.\n- Validates HTTP headers to identify non-browser requests\n- Challenges requests with missing or invalid browser signatures\n- Enabled by default on most Cloudflare plans\n- Low impact on legitimate users with standard browsers",
"Url": "https://hub.prowler.com/checks/cloudflare/zone_browser_integrity_check_enabled"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Browser Integrity Check is enabled by default on most Cloudflare plans. It provides basic protection against requests with invalid or missing browser headers."
}

View File

@@ -0,0 +1,43 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.zone.zone_client import zone_client
class zone_browser_integrity_check_enabled(Check):
"""Ensure that Browser Integrity Check is enabled for Cloudflare zones.
Browser Integrity Check analyzes HTTP headers to identify requests from
bots or clients with missing/invalid browser signatures. It challenges
suspicious requests that don't have valid browser characteristics,
protecting against basic automated attacks and malformed requests.
"""
def execute(self) -> list[CheckReportCloudflare]:
"""Execute the Browser Integrity Check enabled check.
Iterates through all Cloudflare zones and verifies that Browser
Integrity Check is enabled. This feature validates browser headers
to filter out basic bot traffic.
Returns:
A list of CheckReportCloudflare objects with PASS status if Browser
Integrity Check is enabled, or FAIL status if it is disabled.
"""
findings = []
for zone in zone_client.zones.values():
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
browser_check = (zone.settings.browser_check or "").lower()
if browser_check == "on":
report.status = "PASS"
report.status_extended = (
f"Browser Integrity Check is enabled for zone {zone.name}."
)
else:
report.status = "FAIL"
report.status_extended = (
f"Browser Integrity Check is not enabled for zone {zone.name}."
)
findings.append(report)
return findings

View File

@@ -0,0 +1,36 @@
{
"Provider": "cloudflare",
"CheckID": "zone_challenge_passage_configured",
"CheckTitle": "Cloudflare Zone Challenge Passage Is Configured Between 15 and 45 Minutes",
"CheckType": [],
"ServiceName": "zone",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
"ResourceType": "Zone",
"ResourceGroup": "network",
"Description": "**Cloudflare zones** are assessed for **Challenge Passage** (challenge TTL) configuration by checking if it is set between **15 minutes** and **45 minutes** to balance security with user experience.",
"Risk": "Improperly configured **Challenge Passage** can impact security or user experience.\n- **Confidentiality**: TTL set too long may allow attackers extended access after passing initial challenge\n- **Integrity**: security controls become less effective with overly permissive TTL settings\n- **Availability**: TTL set too short causes excessive challenges degrading user experience",
"RelatedUrl": "",
"AdditionalURLs": [
"https://developers.cloudflare.com/waf/tools/challenge-passage/"
],
"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 Challenge Passage\n4. Set the value between 15 and 45 minutes\n5. The default value of 30 minutes is recommended for most use cases",
"Terraform": "```hcl\n# Configure Challenge Passage between 15-45 minutes\nresource \"cloudflare_zone_settings_override\" \"challenge_passage\" {\n zone_id = \"<ZONE_ID>\"\n settings {\n challenge_ttl = 1800 # 30 minutes - recommended default\n }\n}\n```"
},
"Recommendation": {
"Text": "Configure **Challenge Passage** between 15 and 45 minutes.\n- Values below 15 minutes may frustrate legitimate users with excessive challenges\n- Values above 45 minutes give attackers too much time after passing challenges\n- The default Cloudflare value of 30 minutes is recommended for most use cases\n- Adjust based on your specific threat model and user experience requirements",
"Url": "https://hub.prowler.com/checks/cloudflare/zone_challenge_passage_configured"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Challenge Passage determines how long a visitor who passes a challenge can access the site without being challenged again. Setting this value too low can frustrate legitimate users with excessive security challenges, while setting it too high reduces security effectiveness. The default value is 30 minutes."
}

View File

@@ -0,0 +1,45 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.zone.zone_client import zone_client
class zone_challenge_passage_configured(Check):
"""Ensure that Challenge Passage is configured between 15 and 45 minutes for Cloudflare zones.
Challenge Passage (Challenge TTL) determines how long a visitor who has passed
a security challenge can access the site before being challenged again. A value
between 15 and 45 minutes balances security with user experience.
"""
def execute(self) -> list[CheckReportCloudflare]:
"""Execute the Challenge Passage configured check.
Iterates through all Cloudflare zones and verifies that Challenge Passage
is set between 15 and 45 minutes.
Returns:
A list of CheckReportCloudflare objects with PASS status if Challenge
Passage is between 15 and 45 minutes, or FAIL status otherwise.
"""
findings = []
min_minutes = 15
max_minutes = 45
for zone in zone_client.zones.values():
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
# API returns seconds, convert to minutes
challenge_ttl_minutes = zone.settings.challenge_ttl // 60
if min_minutes <= challenge_ttl_minutes <= max_minutes:
report.status = "PASS"
report.status_extended = f"Challenge Passage is set to {challenge_ttl_minutes} minutes for zone {zone.name}."
else:
report.status = "FAIL"
report.status_extended = (
f"Challenge Passage is set to {challenge_ttl_minutes} minutes for zone {zone.name} "
f"(recommended: between {min_minutes} and {max_minutes} minutes)."
)
findings.append(report)
return findings

View File

@@ -0,0 +1,36 @@
{
"Provider": "cloudflare",
"CheckID": "zone_development_mode_disabled",
"CheckTitle": "Development mode is disabled for production zones",
"CheckType": [],
"ServiceName": "zone",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
"ResourceType": "Zone",
"ResourceGroup": "network",
"Description": "**Cloudflare zones** are assessed for **Development Mode** configuration by checking if it is disabled to ensure **caching**, **security features**, and **performance optimizations** are active in production environments.",
"Risk": "With **Development Mode** enabled, Cloudflare bypasses caching and some optimizations.\n- **Confidentiality**: some security features may be affected or bypassed\n- **Integrity**: performance optimizations are disabled impacting site reliability\n- **Availability**: origin server is exposed to increased load without caching protection",
"RelatedUrl": "",
"AdditionalURLs": [
"https://developers.cloudflare.com/cache/reference/development-mode/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Log in to the Cloudflare dashboard and select your account and domain\n2. Go to Caching > Configuration\n3. Scroll to Development Mode\n4. Ensure Development Mode is Off\n5. Note: Development Mode auto-expires after 3 hours if left enabled",
"Terraform": ""
},
"Recommendation": {
"Text": "Disable **Development Mode** for production environments.\n- Use only temporarily during active development when cache bypassing is necessary\n- Development Mode auto-expires after 3 hours\n- Ensure it is manually disabled after development work completes\n- Consider using cache purge or page rules for targeted cache invalidation instead",
"Url": "https://hub.prowler.com/checks/cloudflare/zone_development_mode_disabled"
}
},
"Categories": [
"resilience"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Development Mode temporarily suspends Cloudflare caching and minification. It auto-expires after 3 hours to prevent accidental prolonged use."
}

View File

@@ -0,0 +1,43 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.zone.zone_client import zone_client
class zone_development_mode_disabled(Check):
"""Ensure that Development Mode is disabled for production Cloudflare zones.
Development Mode temporarily bypasses Cloudflare's caching and performance
optimizations, serving content directly from the origin server. While useful
for testing changes, it should be disabled in production to maintain caching,
security features, and performance optimizations.
"""
def execute(self) -> list[CheckReportCloudflare]:
"""Execute the Development Mode disabled check.
Iterates through all Cloudflare zones and verifies that Development Mode
is disabled. When enabled, this mode bypasses caching and can impact
performance and security.
Returns:
A list of CheckReportCloudflare objects with PASS status if Development
Mode is disabled, or FAIL status if it is enabled for the zone.
"""
findings = []
for zone in zone_client.zones.values():
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
dev_mode = (zone.settings.development_mode or "").lower()
if dev_mode == "off" or not dev_mode:
report.status = "PASS"
report.status_extended = (
f"Development mode is disabled for zone {zone.name}."
)
else:
report.status = "FAIL"
report.status_extended = (
f"Development mode is enabled for zone {zone.name}."
)
findings.append(report)
return findings

View File

@@ -0,0 +1,36 @@
{
"Provider": "cloudflare",
"CheckID": "zone_hotlink_protection_enabled",
"CheckTitle": "Hotlink Protection is enabled",
"CheckType": [],
"ServiceName": "zone",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
"ResourceType": "Zone",
"ResourceGroup": "network",
"Description": "**Cloudflare zones** are assessed for **Hotlink Protection** (Scrape Shield) configuration by checking if it is enabled to prevent other websites from directly linking to **images and media**, consuming bandwidth without authorization.",
"Risk": "Without **Hotlink Protection**, external websites can embed your media directly.\n- **Confidentiality**: content may be used without proper attribution or permission\n- **Integrity**: unauthorized use of media may misrepresent your brand\n- **Availability**: bandwidth theft increases costs and may degrade performance",
"RelatedUrl": "",
"AdditionalURLs": [
"https://developers.cloudflare.com/waf/tools/scrape-shield/hotlink-protection/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Log in to the Cloudflare dashboard and select your account and domain\n2. Go to Scrape Shield (or Security > Settings in newer UI)\n3. Scroll to Hotlink Protection\n4. Toggle the setting to On\n5. Review allowed referrers if legitimate integrations require access",
"Terraform": "```hcl\n# Enable Hotlink Protection to prevent bandwidth theft\nresource \"cloudflare_zone_settings_override\" \"hotlink_protection\" {\n zone_id = \"<ZONE_ID>\"\n settings {\n hotlink_protection = \"on\" # Blocks unauthorized embedding of your media resources\n }\n}\n```"
},
"Recommendation": {
"Text": "Enable **Hotlink Protection** to prevent unauthorized embedding of your media.\n- Blocks requests to images when HTTP referer does not match your domain\n- Reduces bandwidth costs from unauthorized embedding\n- Review allowed referrers for legitimate integrations\n- Part of the Scrape Shield feature set for comprehensive protection",
"Url": "https://hub.prowler.com/checks/cloudflare/zone_hotlink_protection_enabled"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Hotlink Protection blocks requests to images when the HTTP referer does not match your domain. May need configuration for legitimate third-party embedding use cases."
}

View File

@@ -0,0 +1,43 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.zone.zone_client import zone_client
class zone_hotlink_protection_enabled(Check):
"""Ensure that Hotlink Protection is enabled for Cloudflare zones.
Hotlink Protection is part of Cloudflare's Scrape Shield suite that prevents
other websites from directly linking to images, videos, and other media files,
which consumes bandwidth without authorization. It blocks requests where the
HTTP referer does not match your domain.
"""
def execute(self) -> list[CheckReportCloudflare]:
"""Execute the Hotlink Protection enabled check.
Iterates through all Cloudflare zones and verifies that Hotlink Protection
is enabled. This feature prevents bandwidth theft by blocking unauthorized
embedding of your media on external sites.
Returns:
A list of CheckReportCloudflare objects with PASS status if Hotlink
Protection is enabled, or FAIL status if it is disabled for the zone.
"""
findings = []
for zone in zone_client.zones.values():
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
hotlink_protection = (zone.settings.hotlink_protection or "").lower()
if hotlink_protection == "on":
report.status = "PASS"
report.status_extended = (
f"Hotlink Protection is enabled for zone {zone.name}."
)
else:
report.status = "FAIL"
report.status_extended = (
f"Hotlink Protection is not enabled for zone {zone.name}."
)
findings.append(report)
return findings

View File

@@ -0,0 +1,36 @@
{
"Provider": "cloudflare",
"CheckID": "zone_ip_geolocation_enabled",
"CheckTitle": "IP Geolocation is enabled",
"CheckType": [],
"ServiceName": "zone",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
"ResourceType": "Zone",
"ResourceGroup": "network",
"Description": "**Cloudflare zones** are assessed for **IP Geolocation** configuration by checking if it is enabled to add the **CF-IPCountry header** to requests, enabling geographic-based access controls, firewall rules, and analytics.",
"Risk": "Without **IP Geolocation**, geographic-based security controls cannot be implemented.\n- **Confidentiality**: unable to restrict access from high-risk regions\n- **Integrity**: cannot enforce geographic data residency requirements\n- **Availability**: limited visibility into traffic origins for threat analysis",
"RelatedUrl": "",
"AdditionalURLs": [
"https://developers.cloudflare.com/network/ip-geolocation/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Log in to the Cloudflare dashboard and select your account and domain\n2. Go to Network\n3. Scroll to IP Geolocation\n4. Toggle the setting to On\n5. Use the CF-IPCountry header in your firewall rules or application logic",
"Terraform": "```hcl\n# Enable IP Geolocation for geographic-based security controls\nresource \"cloudflare_zone_settings_override\" \"ip_geolocation\" {\n zone_id = \"<ZONE_ID>\"\n settings {\n ip_geolocation = \"on\" # Adds CF-IPCountry header for geo-based controls\n }\n}\n```"
},
"Recommendation": {
"Text": "Enable **IP Geolocation** to support geographic-based security policies.\n- Adds CF-IPCountry header to all requests\n- Enables geo-blocking rules in firewall configuration\n- Provides visibility into traffic origins for threat analysis\n- Essential for geographic data residency compliance",
"Url": "https://hub.prowler.com/checks/cloudflare/zone_ip_geolocation_enabled"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "IP Geolocation is essential for implementing geo-blocking or country-specific security rules. The CF-IPCountry header contains ISO 3166-1 Alpha 2 country codes."
}

View File

@@ -0,0 +1,44 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.zone.zone_client import zone_client
class zone_ip_geolocation_enabled(Check):
"""Ensure that IP Geolocation is enabled for Cloudflare zones.
IP Geolocation adds the CF-IPCountry header to all requests, containing the
two-letter country code of the visitor's location. This enables geographic-based
access controls, firewall rules, content customization, and analytics based on
visitor location.
"""
def execute(self) -> list[CheckReportCloudflare]:
"""Execute the IP Geolocation enabled check.
Iterates through all Cloudflare zones and verifies that IP Geolocation
is enabled. This feature adds geographic information to requests for
enhanced security controls and analytics.
Returns:
A list of CheckReportCloudflare objects with PASS status if IP
Geolocation is enabled, or FAIL status if it is disabled for the zone.
"""
findings = []
for zone in zone_client.zones.values():
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
ip_geolocation = (zone.settings.ip_geolocation or "").lower()
if ip_geolocation == "on":
report.status = "PASS"
report.status_extended = (
f"IP Geolocation is enabled for zone {zone.name}."
)
else:
report.status = "FAIL"
report.status_extended = (
f"IP Geolocation is not enabled for zone {zone.name}."
)
findings.append(report)
return findings

View File

@@ -0,0 +1,36 @@
{
"Provider": "cloudflare",
"CheckID": "zone_rate_limiting_enabled",
"CheckTitle": "Rate limiting is configured for the zone",
"CheckType": [],
"ServiceName": "zone",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Zone",
"ResourceGroup": "network",
"Description": "**Cloudflare zones** are assessed for **Rate Limiting** configuration by checking if rules are configured to protect against **DDoS attacks**, **brute force attempts**, and **API abuse**.",
"Risk": "Without **Rate Limiting**, applications are vulnerable to volumetric attacks.\n- **Confidentiality**: credential brute forcing can compromise user accounts\n- **Integrity**: API abuse can manipulate data through excessive requests\n- **Availability**: volumetric attacks can exhaust resources causing service degradation",
"RelatedUrl": "",
"AdditionalURLs": [
"https://developers.cloudflare.com/waf/rate-limiting-rules/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Log in to the Cloudflare dashboard and select your account and domain\n2. Go to Security > WAF > Rate limiting rules\n3. Create a new rate limiting rule\n4. Configure thresholds based on expected traffic patterns\n5. Apply stricter limits to sensitive endpoints like authentication and APIs",
"Terraform": "```hcl\n# Configure Rate Limiting to protect against volumetric attacks\nresource \"cloudflare_ruleset\" \"rate_limit\" {\n zone_id = \"<ZONE_ID>\"\n name = \"Rate limiting\"\n kind = \"zone\"\n phase = \"http_ratelimit\"\n rules {\n action = \"block\"\n ratelimit {\n characteristics = [\"ip.src\"]\n period = 60\n requests_per_period = 100\n mitigation_timeout = 600\n }\n expression = \"true\"\n description = \"Rate limit all requests\"\n }\n}\n```"
},
"Recommendation": {
"Text": "Implement **Rate Limiting** as part of defense in depth.\n- Configure thresholds based on expected traffic patterns\n- Apply stricter limits to sensitive endpoints like authentication and APIs\n- Use different rate limits for different paths or endpoints\n- Monitor rate limiting analytics to fine-tune thresholds",
"Url": "https://hub.prowler.com/checks/cloudflare/zone_rate_limiting_enabled"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Rate limiting rules are configured in the http_ratelimit phase. Consider different thresholds for different types of endpoints based on their sensitivity and expected usage patterns."
}

View File

@@ -0,0 +1,50 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.zone.zone_client import zone_client
class zone_rate_limiting_enabled(Check):
"""Ensure that Rate Limiting is configured for Cloudflare zones.
Rate Limiting protects against DDoS attacks, brute force attempts, and API
abuse by limiting the number of requests from a single source within a specified
time window. Rules are configured in the http_ratelimit phase and help maintain
service availability under high-traffic conditions.
"""
def execute(self) -> list[CheckReportCloudflare]:
"""Execute the Rate Limiting enabled check.
Iterates through all Cloudflare zones and verifies that at least one
enabled rate limiting rule exists.
Returns:
A list of CheckReportCloudflare objects with PASS status if rate
limiting rules are configured, or FAIL status if no rules exist.
"""
findings = []
for zone in zone_client.zones.values():
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
# Get enabled rate limiting rules for this zone
enabled_rules = [rule for rule in zone.rate_limit_rules if rule.enabled]
if enabled_rules:
report.status = "PASS"
rules_str = ", ".join(
rule.description or rule.id for rule in enabled_rules
)
report.status_extended = (
f"Rate limiting is configured for zone {zone.name}: {rules_str}."
)
else:
report.status = "FAIL"
report.status_extended = (
f"No rate limiting rules configured for zone {zone.name}."
)
findings.append(report)
return findings

View File

@@ -7,6 +7,16 @@ from prowler.providers.cloudflare.lib.service.service import CloudflareService
from prowler.providers.cloudflare.models import CloudflareAccount from prowler.providers.cloudflare.models import CloudflareAccount
class CloudflareRateLimitRule(BaseModel):
"""Cloudflare rate limiting rule representation."""
id: str
description: Optional[str] = None
action: Optional[str] = None
enabled: bool = True
expression: Optional[str] = None
class Zone(CloudflareService): class Zone(CloudflareService):
"""Retrieve Cloudflare zones with security-relevant settings.""" """Retrieve Cloudflare zones with security-relevant settings."""
@@ -17,6 +27,8 @@ class Zone(CloudflareService):
self._get_zones_settings() self._get_zones_settings()
self._get_zones_dnssec() self._get_zones_dnssec()
self._get_zones_universal_ssl() self._get_zones_universal_ssl()
self._get_zones_rate_limit_rules()
self._get_zones_bot_management()
def _list_zones(self) -> None: def _list_zones(self) -> None:
"""List all Cloudflare zones with their basic information.""" """List all Cloudflare zones with their basic information."""
@@ -122,6 +134,63 @@ class Zone(CloudflareService):
f"{zone.id} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}" f"{zone.id} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
) )
def _get_zones_rate_limit_rules(self) -> None:
"""Get rate limiting rules for all zones."""
logger.info("Zone - Getting rate limit rules...")
for zone in self.zones.values():
try:
seen_ruleset_ids: set[str] = set()
for ruleset in self.client.rulesets.list(zone_id=zone.id):
ruleset_id = getattr(ruleset, "id", "")
if ruleset_id in seen_ruleset_ids:
break
seen_ruleset_ids.add(ruleset_id)
phase = getattr(ruleset, "phase", "")
if phase == "http_ratelimit":
try:
ruleset_detail = self.client.rulesets.get(
ruleset_id=ruleset_id, zone_id=zone.id
)
rules = getattr(ruleset_detail, "rules", []) or []
seen_rule_ids: set[str] = set()
for rule in rules:
rule_id = getattr(rule, "id", "")
if rule_id in seen_rule_ids:
break
seen_rule_ids.add(rule_id)
zone.rate_limit_rules.append(
CloudflareRateLimitRule(
id=rule_id,
description=getattr(rule, "description", None),
action=getattr(rule, "action", None),
enabled=getattr(rule, "enabled", True),
expression=getattr(rule, "expression", None),
)
)
except Exception as error:
logger.debug(
f"{zone.id} ruleset {ruleset_id} -- {error.__class__.__name__}: {error}"
)
except Exception as error:
logger.error(
f"{zone.id} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _get_zones_bot_management(self) -> None:
"""Get Bot Management settings for all zones."""
logger.info("Zone - Getting Bot Management settings...")
for zone in self.zones.values():
try:
bot_management = self.client.bot_management.get(zone_id=zone.id)
zone.settings.bot_fight_mode_enabled = getattr(
bot_management, "fight_mode", False
)
except Exception as error:
logger.error(
f"{zone.id} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
)
def _get_zone_setting(self, zone_id: str, setting_id: str): def _get_zone_setting(self, zone_id: str, setting_id: str):
"""Get a single zone setting by ID.""" """Get a single zone setting by ID."""
try: try:
@@ -168,7 +237,7 @@ class Zone(CloudflareService):
waf=settings.get("waf"), waf=settings.get("waf"),
security_level=settings.get("security_level"), security_level=settings.get("security_level"),
browser_check=settings.get("browser_check"), browser_check=settings.get("browser_check"),
challenge_ttl=settings.get("challenge_ttl"), challenge_ttl=settings.get("challenge_ttl") or 0,
ip_geolocation=settings.get("ip_geolocation"), ip_geolocation=settings.get("ip_geolocation"),
email_obfuscation=settings.get("email_obfuscation"), email_obfuscation=settings.get("email_obfuscation"),
server_side_exclude=settings.get("server_side_exclude"), server_side_exclude=settings.get("server_side_exclude"),
@@ -241,6 +310,8 @@ class CloudflareZoneSettings(BaseModel):
# Zone state # Zone state
development_mode: Optional[str] = None development_mode: Optional[str] = None
always_online: Optional[str] = None always_online: Optional[str] = None
# Bot management
bot_fight_mode_enabled: bool = False
class CloudflareZone(BaseModel): class CloudflareZone(BaseModel):
@@ -254,3 +325,4 @@ class CloudflareZone(BaseModel):
plan: Optional[str] = None plan: Optional[str] = None
settings: CloudflareZoneSettings = Field(default_factory=CloudflareZoneSettings) settings: CloudflareZoneSettings = Field(default_factory=CloudflareZoneSettings)
dnssec_status: Optional[str] = None dnssec_status: Optional[str] = None
rate_limit_rules: list[CloudflareRateLimitRule] = Field(default_factory=list)

View File

@@ -0,0 +1,36 @@
{
"Provider": "cloudflare",
"CheckID": "zone_waf_enabled",
"CheckTitle": "WAF is enabled",
"CheckType": [],
"ServiceName": "zone",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Zone",
"ResourceGroup": "network",
"Description": "**Cloudflare zones** are assessed for **Web Application Firewall (WAF)** configuration by checking if it is enabled to protect against common web vulnerabilities including **SQL injection**, **XSS**, and **OWASP Top 10** threats.",
"Risk": "Without **WAF**, web applications are exposed to common attack vectors.\n- **Confidentiality**: SQL injection attacks can exfiltrate sensitive database contents\n- **Integrity**: XSS attacks can modify page content and steal session tokens\n- **Availability**: application-layer attacks can cause service disruption",
"RelatedUrl": "",
"AdditionalURLs": [
"https://developers.cloudflare.com/waf/"
],
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "1. Log in to the Cloudflare dashboard and select your account and domain\n2. Go to Security > WAF\n3. Enable managed rulesets (OWASP, Cloudflare Managed Ruleset)\n4. Configure custom rules based on your application's specific threat model\n5. Monitor WAF analytics to tune rules and reduce false positives",
"Terraform": "```hcl\n# Enable WAF managed rulesets for comprehensive protection\nresource \"cloudflare_ruleset\" \"waf_managed\" {\n zone_id = \"<ZONE_ID>\"\n name = \"WAF Managed Rules\"\n kind = \"zone\"\n phase = \"http_request_firewall_managed\"\n rules {\n action = \"execute\"\n action_parameters {\n id = \"efb7b8c949ac4650a09736fc376e9aee\" # Cloudflare Managed Ruleset\n }\n expression = \"true\"\n description = \"Execute Cloudflare Managed Ruleset\"\n }\n}\n```"
},
"Recommendation": {
"Text": "Enable **WAF** as a critical layer of defense for web applications.\n- Deploy managed rulesets for protection against OWASP Top 10 vulnerabilities\n- Create custom rules based on your application's specific threat model\n- Monitor WAF analytics to tune rules and reduce false positives\n- Combine with rate limiting and bot protection for comprehensive security",
"Url": "https://hub.prowler.com/checks/cloudflare/zone_waf_enabled"
}
},
"Categories": [
"vulnerabilities"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "WAF is available on Pro, Business, and Enterprise plans. Configure managed rulesets and create custom rules to match your application's specific security requirements."
}

View File

@@ -0,0 +1,40 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.zone.zone_client import zone_client
class zone_waf_enabled(Check):
"""Ensure that WAF is enabled for Cloudflare zones.
The Web Application Firewall (WAF) protects against common web vulnerabilities
including SQL injection, cross-site scripting (XSS), and other OWASP Top 10
threats. When enabled, it inspects HTTP requests and blocks malicious traffic
before it reaches the origin server.
"""
def execute(self) -> list[CheckReportCloudflare]:
"""Execute the WAF enabled check.
Iterates through all Cloudflare zones and verifies that the Web Application
Firewall is enabled. The WAF provides essential protection against common
web application attacks.
Returns:
A list of CheckReportCloudflare objects with PASS status if WAF is
enabled, or FAIL status if it is disabled for the zone.
"""
findings = []
for zone in zone_client.zones.values():
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
waf_setting = (zone.settings.waf or "").lower()
if waf_setting == "on":
report.status = "PASS"
report.status_extended = f"WAF is enabled for zone {zone.name}."
else:
report.status = "FAIL"
report.status_extended = f"WAF is not enabled for zone {zone.name}."
findings.append(report)
return findings

View File

@@ -0,0 +1,138 @@
from unittest import mock
from prowler.providers.cloudflare.services.zone.zone_service import (
CloudflareZone,
CloudflareZoneSettings,
)
from tests.providers.cloudflare.cloudflare_fixtures import (
ZONE_ID,
ZONE_NAME,
set_mocked_cloudflare_provider,
)
class Test_zone_always_online_disabled:
def test_no_zones(self):
zone_client = mock.MagicMock
zone_client.zones = {}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_always_online_disabled.zone_always_online_disabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_always_online_disabled.zone_always_online_disabled import (
zone_always_online_disabled,
)
check = zone_always_online_disabled()
result = check.execute()
assert len(result) == 0
def test_zone_always_online_disabled(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
always_online="off",
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_always_online_disabled.zone_always_online_disabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_always_online_disabled.zone_always_online_disabled import (
zone_always_online_disabled,
)
check = zone_always_online_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 "Always Online is disabled" in result[0].status_extended
def test_zone_always_online_enabled(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
always_online="on",
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_always_online_disabled.zone_always_online_disabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_always_online_disabled.zone_always_online_disabled import (
zone_always_online_disabled,
)
check = zone_always_online_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "Always Online is enabled" in result[0].status_extended
def test_zone_always_online_none(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
always_online=None,
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_always_online_disabled.zone_always_online_disabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_always_online_disabled.zone_always_online_disabled import (
zone_always_online_disabled,
)
check = zone_always_online_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"

View File

@@ -0,0 +1,106 @@
from unittest import mock
from prowler.providers.cloudflare.services.zone.zone_service import (
CloudflareZone,
CloudflareZoneSettings,
)
from tests.providers.cloudflare.cloudflare_fixtures import (
ZONE_ID,
ZONE_NAME,
set_mocked_cloudflare_provider,
)
class Test_zone_bot_fight_mode_enabled:
def test_no_zones(self):
zone_client = mock.MagicMock
zone_client.zones = {}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_bot_fight_mode_enabled.zone_bot_fight_mode_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_bot_fight_mode_enabled.zone_bot_fight_mode_enabled import (
zone_bot_fight_mode_enabled,
)
check = zone_bot_fight_mode_enabled()
result = check.execute()
assert len(result) == 0
def test_zone_bot_fight_mode_enabled(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
bot_fight_mode_enabled=True,
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_bot_fight_mode_enabled.zone_bot_fight_mode_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_bot_fight_mode_enabled.zone_bot_fight_mode_enabled import (
zone_bot_fight_mode_enabled,
)
check = zone_bot_fight_mode_enabled()
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 "Bot Fight Mode" in result[0].status_extended
assert "enabled" in result[0].status_extended
def test_zone_bot_fight_mode_disabled(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
bot_fight_mode_enabled=False,
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_bot_fight_mode_enabled.zone_bot_fight_mode_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_bot_fight_mode_enabled.zone_bot_fight_mode_enabled import (
zone_bot_fight_mode_enabled,
)
check = zone_bot_fight_mode_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "not enabled" in result[0].status_extended

View File

@@ -0,0 +1,139 @@
from unittest import mock
from prowler.providers.cloudflare.services.zone.zone_service import (
CloudflareZone,
CloudflareZoneSettings,
)
from tests.providers.cloudflare.cloudflare_fixtures import (
ZONE_ID,
ZONE_NAME,
set_mocked_cloudflare_provider,
)
class Test_zone_browser_integrity_check_enabled:
def test_no_zones(self):
zone_client = mock.MagicMock
zone_client.zones = {}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_browser_integrity_check_enabled.zone_browser_integrity_check_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_browser_integrity_check_enabled.zone_browser_integrity_check_enabled import (
zone_browser_integrity_check_enabled,
)
check = zone_browser_integrity_check_enabled()
result = check.execute()
assert len(result) == 0
def test_zone_browser_integrity_check_enabled(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
browser_check="on",
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_browser_integrity_check_enabled.zone_browser_integrity_check_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_browser_integrity_check_enabled.zone_browser_integrity_check_enabled import (
zone_browser_integrity_check_enabled,
)
check = zone_browser_integrity_check_enabled()
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 "Browser Integrity Check" in result[0].status_extended
assert "enabled" in result[0].status_extended
def test_zone_browser_integrity_check_disabled(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
browser_check="off",
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_browser_integrity_check_enabled.zone_browser_integrity_check_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_browser_integrity_check_enabled.zone_browser_integrity_check_enabled import (
zone_browser_integrity_check_enabled,
)
check = zone_browser_integrity_check_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "not enabled" in result[0].status_extended
def test_zone_browser_integrity_check_none(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
browser_check=None,
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_browser_integrity_check_enabled.zone_browser_integrity_check_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_browser_integrity_check_enabled.zone_browser_integrity_check_enabled import (
zone_browser_integrity_check_enabled,
)
check = zone_browser_integrity_check_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"

View File

@@ -0,0 +1,242 @@
from unittest import mock
from prowler.providers.cloudflare.services.zone.zone_service import (
CloudflareZone,
CloudflareZoneSettings,
)
from tests.providers.cloudflare.cloudflare_fixtures import (
ZONE_ID,
ZONE_NAME,
set_mocked_cloudflare_provider,
)
class Test_zone_challenge_passage_configured:
def test_no_zones(self):
zone_client = mock.MagicMock
zone_client.zones = {}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_challenge_passage_configured.zone_challenge_passage_configured.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_challenge_passage_configured.zone_challenge_passage_configured import (
zone_challenge_passage_configured,
)
check = zone_challenge_passage_configured()
result = check.execute()
assert len(result) == 0
def test_zone_challenge_passage_at_min(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
challenge_ttl=900, # 15 minutes - minimum recommended
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_challenge_passage_configured.zone_challenge_passage_configured.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_challenge_passage_configured.zone_challenge_passage_configured import (
zone_challenge_passage_configured,
)
check = zone_challenge_passage_configured()
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 "15 minutes" in result[0].status_extended
def test_zone_challenge_passage_at_max(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
challenge_ttl=2700, # 45 minutes - maximum recommended
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_challenge_passage_configured.zone_challenge_passage_configured.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_challenge_passage_configured.zone_challenge_passage_configured import (
zone_challenge_passage_configured,
)
check = zone_challenge_passage_configured()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "45 minutes" in result[0].status_extended
def test_zone_challenge_passage_default(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
challenge_ttl=1800, # 30 minutes - default and secure
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_challenge_passage_configured.zone_challenge_passage_configured.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_challenge_passage_configured.zone_challenge_passage_configured import (
zone_challenge_passage_configured,
)
check = zone_challenge_passage_configured()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert "30 minutes" in result[0].status_extended
def test_zone_challenge_passage_too_short(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
challenge_ttl=300, # 5 minutes - too short
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_challenge_passage_configured.zone_challenge_passage_configured.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_challenge_passage_configured.zone_challenge_passage_configured import (
zone_challenge_passage_configured,
)
check = zone_challenge_passage_configured()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "5 minutes" in result[0].status_extended
assert "recommended" in result[0].status_extended
def test_zone_challenge_passage_too_long(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
challenge_ttl=3600, # 60 minutes - exceeds recommended
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_challenge_passage_configured.zone_challenge_passage_configured.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_challenge_passage_configured.zone_challenge_passage_configured import (
zone_challenge_passage_configured,
)
check = zone_challenge_passage_configured()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "60 minutes" in result[0].status_extended
assert "recommended" in result[0].status_extended
def test_zone_challenge_passage_none(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
challenge_ttl=None,
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_challenge_passage_configured.zone_challenge_passage_configured.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_challenge_passage_configured.zone_challenge_passage_configured import (
zone_challenge_passage_configured,
)
check = zone_challenge_passage_configured()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"

View File

@@ -0,0 +1,140 @@
from unittest import mock
from prowler.providers.cloudflare.services.zone.zone_service import (
CloudflareZone,
CloudflareZoneSettings,
)
from tests.providers.cloudflare.cloudflare_fixtures import (
ZONE_ID,
ZONE_NAME,
set_mocked_cloudflare_provider,
)
class Test_zone_development_mode_disabled:
def test_no_zones(self):
zone_client = mock.MagicMock
zone_client.zones = {}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_development_mode_disabled.zone_development_mode_disabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_development_mode_disabled.zone_development_mode_disabled import (
zone_development_mode_disabled,
)
check = zone_development_mode_disabled()
result = check.execute()
assert len(result) == 0
def test_zone_development_mode_disabled(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
development_mode="off",
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_development_mode_disabled.zone_development_mode_disabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_development_mode_disabled.zone_development_mode_disabled import (
zone_development_mode_disabled,
)
check = zone_development_mode_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 "Development mode is disabled" in result[0].status_extended
def test_zone_development_mode_enabled(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
development_mode="on",
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_development_mode_disabled.zone_development_mode_disabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_development_mode_disabled.zone_development_mode_disabled import (
zone_development_mode_disabled,
)
check = zone_development_mode_disabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "Development mode is enabled" in result[0].status_extended
assert "bypasses" in result[0].status_extended
def test_zone_development_mode_none(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
development_mode=None,
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_development_mode_disabled.zone_development_mode_disabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_development_mode_disabled.zone_development_mode_disabled import (
zone_development_mode_disabled,
)
check = zone_development_mode_disabled()
result = check.execute()
assert len(result) == 1
# None or empty string should be treated as disabled (PASS)
assert result[0].status == "PASS"

View File

@@ -0,0 +1,138 @@
from unittest import mock
from prowler.providers.cloudflare.services.zone.zone_service import (
CloudflareZone,
CloudflareZoneSettings,
)
from tests.providers.cloudflare.cloudflare_fixtures import (
ZONE_ID,
ZONE_NAME,
set_mocked_cloudflare_provider,
)
class Test_zone_hotlink_protection_enabled:
def test_no_zones(self):
zone_client = mock.MagicMock
zone_client.zones = {}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_hotlink_protection_enabled.zone_hotlink_protection_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_hotlink_protection_enabled.zone_hotlink_protection_enabled import (
zone_hotlink_protection_enabled,
)
check = zone_hotlink_protection_enabled()
result = check.execute()
assert len(result) == 0
def test_zone_hotlink_protection_enabled(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
hotlink_protection="on",
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_hotlink_protection_enabled.zone_hotlink_protection_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_hotlink_protection_enabled.zone_hotlink_protection_enabled import (
zone_hotlink_protection_enabled,
)
check = zone_hotlink_protection_enabled()
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 "Hotlink Protection is enabled" in result[0].status_extended
def test_zone_hotlink_protection_disabled(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
hotlink_protection="off",
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_hotlink_protection_enabled.zone_hotlink_protection_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_hotlink_protection_enabled.zone_hotlink_protection_enabled import (
zone_hotlink_protection_enabled,
)
check = zone_hotlink_protection_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "Hotlink Protection is not enabled" in result[0].status_extended
def test_zone_hotlink_protection_none(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
hotlink_protection=None,
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_hotlink_protection_enabled.zone_hotlink_protection_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_hotlink_protection_enabled.zone_hotlink_protection_enabled import (
zone_hotlink_protection_enabled,
)
check = zone_hotlink_protection_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"

View File

@@ -0,0 +1,138 @@
from unittest import mock
from prowler.providers.cloudflare.services.zone.zone_service import (
CloudflareZone,
CloudflareZoneSettings,
)
from tests.providers.cloudflare.cloudflare_fixtures import (
ZONE_ID,
ZONE_NAME,
set_mocked_cloudflare_provider,
)
class Test_zone_ip_geolocation_enabled:
def test_no_zones(self):
zone_client = mock.MagicMock
zone_client.zones = {}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_ip_geolocation_enabled.zone_ip_geolocation_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_ip_geolocation_enabled.zone_ip_geolocation_enabled import (
zone_ip_geolocation_enabled,
)
check = zone_ip_geolocation_enabled()
result = check.execute()
assert len(result) == 0
def test_zone_ip_geolocation_enabled(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
ip_geolocation="on",
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_ip_geolocation_enabled.zone_ip_geolocation_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_ip_geolocation_enabled.zone_ip_geolocation_enabled import (
zone_ip_geolocation_enabled,
)
check = zone_ip_geolocation_enabled()
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 "IP Geolocation is enabled" in result[0].status_extended
def test_zone_ip_geolocation_disabled(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
ip_geolocation="off",
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_ip_geolocation_enabled.zone_ip_geolocation_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_ip_geolocation_enabled.zone_ip_geolocation_enabled import (
zone_ip_geolocation_enabled,
)
check = zone_ip_geolocation_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "IP Geolocation is not enabled" in result[0].status_extended
def test_zone_ip_geolocation_none(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
ip_geolocation=None,
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_ip_geolocation_enabled.zone_ip_geolocation_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_ip_geolocation_enabled.zone_ip_geolocation_enabled import (
zone_ip_geolocation_enabled,
)
check = zone_ip_geolocation_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"

View File

@@ -0,0 +1,193 @@
from unittest import mock
from prowler.providers.cloudflare.services.zone.zone_service import (
CloudflareRateLimitRule,
CloudflareZone,
CloudflareZoneSettings,
)
from tests.providers.cloudflare.cloudflare_fixtures import (
ZONE_ID,
ZONE_NAME,
set_mocked_cloudflare_provider,
)
class Test_zone_rate_limiting_enabled:
def test_no_zones(self):
zone_client = mock.MagicMock
zone_client.zones = {}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_rate_limiting_enabled.zone_rate_limiting_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_rate_limiting_enabled.zone_rate_limiting_enabled import (
zone_rate_limiting_enabled,
)
check = zone_rate_limiting_enabled()
result = check.execute()
assert len(result) == 0
def test_zone_with_rate_limiting_rules(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(),
rate_limit_rules=[
CloudflareRateLimitRule(
id="rule-1",
description="API Rate Limit",
action="block",
enabled=True,
expression="(http.request.uri.path contains '/api/')",
)
],
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_rate_limiting_enabled.zone_rate_limiting_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_rate_limiting_enabled.zone_rate_limiting_enabled import (
zone_rate_limiting_enabled,
)
check = zone_rate_limiting_enabled()
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 "Rate limiting is configured" in result[0].status_extended
def test_zone_with_multiple_rate_limiting_rules(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(),
rate_limit_rules=[
CloudflareRateLimitRule(
id="rule-1",
description="API Rate Limit",
enabled=True,
),
CloudflareRateLimitRule(
id="rule-2",
description="Login Rate Limit",
enabled=True,
),
],
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_rate_limiting_enabled.zone_rate_limiting_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_rate_limiting_enabled.zone_rate_limiting_enabled import (
zone_rate_limiting_enabled,
)
check = zone_rate_limiting_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
def test_zone_without_rate_limiting_rules(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(),
rate_limit_rules=[],
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_rate_limiting_enabled.zone_rate_limiting_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_rate_limiting_enabled.zone_rate_limiting_enabled import (
zone_rate_limiting_enabled,
)
check = zone_rate_limiting_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "No rate limiting rules configured" in result[0].status_extended
def test_zone_with_disabled_rate_limiting_rules(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(),
rate_limit_rules=[
CloudflareRateLimitRule(
id="rule-1",
description="Disabled Rule",
enabled=False,
)
],
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_rate_limiting_enabled.zone_rate_limiting_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_rate_limiting_enabled.zone_rate_limiting_enabled import (
zone_rate_limiting_enabled,
)
check = zone_rate_limiting_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"

View File

@@ -0,0 +1,138 @@
from unittest import mock
from prowler.providers.cloudflare.services.zone.zone_service import (
CloudflareZone,
CloudflareZoneSettings,
)
from tests.providers.cloudflare.cloudflare_fixtures import (
ZONE_ID,
ZONE_NAME,
set_mocked_cloudflare_provider,
)
class Test_zone_waf_enabled:
def test_no_zones(self):
zone_client = mock.MagicMock
zone_client.zones = {}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_waf_enabled.zone_waf_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_waf_enabled.zone_waf_enabled import (
zone_waf_enabled,
)
check = zone_waf_enabled()
result = check.execute()
assert len(result) == 0
def test_zone_waf_enabled(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
waf="on",
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_waf_enabled.zone_waf_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_waf_enabled.zone_waf_enabled import (
zone_waf_enabled,
)
check = zone_waf_enabled()
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 "WAF is enabled" in result[0].status_extended
def test_zone_waf_disabled(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
waf="off",
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_waf_enabled.zone_waf_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_waf_enabled.zone_waf_enabled import (
zone_waf_enabled,
)
check = zone_waf_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"
assert "WAF is not enabled" in result[0].status_extended
def test_zone_waf_none(self):
zone_client = mock.MagicMock
zone_client.zones = {
ZONE_ID: CloudflareZone(
id=ZONE_ID,
name=ZONE_NAME,
status="active",
paused=False,
settings=CloudflareZoneSettings(
waf=None,
),
)
}
with (
mock.patch(
"prowler.providers.common.provider.Provider.get_global_provider",
return_value=set_mocked_cloudflare_provider(),
),
mock.patch(
"prowler.providers.cloudflare.services.zone.zone_waf_enabled.zone_waf_enabled.zone_client",
new=zone_client,
),
):
from prowler.providers.cloudflare.services.zone.zone_waf_enabled.zone_waf_enabled import (
zone_waf_enabled,
)
check = zone_waf_enabled()
result = check.execute()
assert len(result) == 1
assert result[0].status == "FAIL"