feat(cloudflare): add TLS/SSL and email security checks for zones

Adds 9 additional security checks for Cloudflare zones:

TLS/SSL checks:
- zones_tls_1_3_enabled: Validates TLS 1.3 is enabled
- zones_hsts_include_subdomains: Ensures HSTS includes subdomains
- zones_automatic_https_rewrites_enabled: Validates automatic HTTPS rewrites
- zones_universal_ssl_enabled: Ensures Universal SSL is enabled

Email security checks:
- zones_dmarc_record_exists: Validates DMARC record exists
- zones_spf_record_exists: Validates SPF record exists
- zones_caa_record_exists: Validates CAA record exists
- zones_email_obfuscation_enabled: Ensures email obfuscation is enabled

Security configuration:
- zones_security_level: Validates security level configuration
This commit is contained in:
HugoPBrito
2025-12-03 11:45:25 +01:00
parent 2636351f5d
commit 6202b45a97
27 changed files with 535 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
{
"Provider": "cloudflare",
"CheckID": "zones_automatic_https_rewrites_enabled",
"CheckTitle": "Automatic HTTPS Rewrites is enabled to resolve mixed content issues",
"CheckType": [],
"ServiceName": "zones",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Zone",
"Description": "Verifies that Automatic HTTPS Rewrites is enabled to automatically rewrite insecure HTTP links to HTTPS, resolving mixed content issues and enhancing site security.",
"Risk": "Without Automatic HTTPS Rewrites, pages may contain mixed content where HTTP resources load over HTTPS pages. Browsers block or warn about mixed content, degrading user experience and leaving resources vulnerable to interception.",
"RelatedUrl": "https://developers.cloudflare.com/ssl/edge-certificates/additional-options/automatic-https-rewrites/",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": "resource \"cloudflare_zone_settings_override\" \"example\" {\n zone_id = var.zone_id\n settings {\n automatic_https_rewrites = \"on\"\n }\n}"
},
"Recommendation": {
"Text": "Enable Automatic HTTPS Rewrites as part of a comprehensive HTTPS strategy. Combine with Always Use HTTPS and HSTS to enforce encrypted transport for all traffic following defense in depth principles.",
"Url": "https://hub.prowler.com/checks/zones_automatic_https_rewrites_enabled"
}
},
"Categories": [
"encryption"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "This feature works best when combined with Always Use HTTPS to ensure the entire site is served over HTTPS."
}

View File

@@ -0,0 +1,27 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.zones.zones_client import zones_client
class zones_automatic_https_rewrites_enabled(Check):
def execute(self) -> list[CheckReportCloudflare]:
findings = []
for zone in zones_client.zones:
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
automatic_https_rewrites = (
zone.settings.automatic_https_rewrites or ""
).lower()
if automatic_https_rewrites == "on":
report.status = "PASS"
report.status_extended = (
f"Automatic HTTPS Rewrites is enabled for zone {zone.name}."
)
else:
report.status = "FAIL"
report.status_extended = (
f"Automatic HTTPS Rewrites is not enabled for zone {zone.name}."
)
findings.append(report)
return findings

View File

@@ -0,0 +1,32 @@
{
"Provider": "cloudflare",
"CheckID": "zones_caa_record_exists",
"CheckTitle": "CAA record exists to restrict certificate authority issuance",
"CheckType": [],
"ServiceName": "zones",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
"ResourceType": "Zone",
"Description": "Verifies that a CAA (Certificate Authority Authorization) DNS record exists to specify which certificate authorities are permitted to issue certificates for the domain.",
"Risk": "Without CAA records, any certificate authority can issue certificates for the domain, increasing the risk of unauthorized certificate issuance through CA compromise or social engineering attacks against CAs.",
"RelatedUrl": "https://developers.cloudflare.com/ssl/edge-certificates/caa-records/",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": "resource \"cloudflare_record\" \"caa\" {\n zone_id = var.zone_id\n name = \"@\"\n type = \"CAA\"\n data {\n flags = \"0\"\n tag = \"issue\"\n value = \"letsencrypt.org\"\n }\n}"
},
"Recommendation": {
"Text": "Add CAA records specifying authorized certificate authorities for your domain. This provides an additional layer of defense against unauthorized certificate issuance as part of a comprehensive PKI security strategy.",
"Url": "https://hub.prowler.com/checks/zones_caa_record_exists"
}
},
"Categories": [
"encryption"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "CAA records help prevent unauthorized SSL/TLS certificate issuance."
}

View File

@@ -0,0 +1,34 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.dns.dns_client import dns_client
from prowler.providers.cloudflare.services.zones.zones_client import zones_client
class zones_caa_record_exists(Check):
def execute(self) -> list[CheckReportCloudflare]:
findings = []
for zone in zones_client.zones:
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
# CAA records restrict which CAs can issue certificates
caa_records = [
record
for record in dns_client.records
if record.zone.id == zone.id and record.type == "CAA"
]
if caa_records:
report.status = "PASS"
report.status_extended = (
f"CAA record exists for zone {zone.name} "
f"({len(caa_records)} record(s))."
)
else:
report.status = "FAIL"
report.status_extended = f"No CAA record found for zone {zone.name}."
findings.append(report)
return findings

View File

@@ -0,0 +1,32 @@
{
"Provider": "cloudflare",
"CheckID": "zones_dmarc_record_exists",
"CheckTitle": "DMARC record exists for email authentication and reporting",
"CheckType": [],
"ServiceName": "zones",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Zone",
"Description": "Verifies that a DMARC (Domain-based Message Authentication, Reporting, and Conformance) TXT record exists to define email authentication policy and enable reporting on spoofing attempts.",
"Risk": "Without DMARC, there is no policy enforcement for SPF and DKIM failures, allowing attackers to spoof emails from your domain for phishing campaigns while you receive no visibility into abuse attempts.",
"RelatedUrl": "https://developers.cloudflare.com/dns/manage-dns-records/how-to/email-records/",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": "resource \"cloudflare_record\" \"dmarc\" {\n zone_id = var.zone_id\n name = \"_dmarc\"\n type = \"TXT\"\n value = \"v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com\"\n}"
},
"Recommendation": {
"Text": "Implement DMARC as part of a complete email authentication strategy with SPF and DKIM. Start with a monitoring policy (p=none) to gather data, then progressively enforce stricter policies (quarantine, reject).",
"Url": "https://hub.prowler.com/checks/zones_dmarc_record_exists"
}
},
"Categories": [
"email-security"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "DMARC records are TXT records at _dmarc subdomain starting with 'v=DMARC1'."
}

View File

@@ -0,0 +1,34 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.dns.dns_client import dns_client
from prowler.providers.cloudflare.services.zones.zones_client import zones_client
class zones_dmarc_record_exists(Check):
def execute(self) -> list[CheckReportCloudflare]:
findings = []
for zone in zones_client.zones:
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
# DMARC records are TXT records at _dmarc subdomain starting with "v=DMARC1"
dmarc_records = [
record
for record in dns_client.records
if record.zone.id == zone.id
and record.type == "TXT"
and record.name.startswith("_dmarc")
and "v=DMARC1" in record.content.upper()
]
if dmarc_records:
report.status = "PASS"
report.status_extended = f"DMARC record exists for zone {zone.name}."
else:
report.status = "FAIL"
report.status_extended = f"No DMARC record found for zone {zone.name}."
findings.append(report)
return findings

View File

@@ -0,0 +1,32 @@
{
"Provider": "cloudflare",
"CheckID": "zones_email_obfuscation_enabled",
"CheckTitle": "Email Obfuscation is enabled to protect against email harvesting",
"CheckType": [],
"ServiceName": "zones",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "low",
"ResourceType": "Zone",
"Description": "Verifies that Email Obfuscation (Scrape Shield) is enabled to protect email addresses on the website from automated harvesting by bots and spammers.",
"Risk": "Without Email Obfuscation, email addresses displayed on the website can be harvested by bots, leading to spam, phishing attacks targeting employees or users, and potential social engineering attempts.",
"RelatedUrl": "https://developers.cloudflare.com/waf/tools/scrape-shield/email-address-obfuscation/",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": "resource \"cloudflare_zone_settings_override\" \"example\" {\n zone_id = var.zone_id\n settings {\n email_obfuscation = \"on\"\n }\n}"
},
"Recommendation": {
"Text": "Enable Email Obfuscation as part of anti-scraping protections. The feature automatically encodes email addresses for bots while keeping them visible and functional for human visitors.",
"Url": "https://hub.prowler.com/checks/zones_email_obfuscation_enabled"
}
},
"Categories": [
"internet-exposed"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Email Obfuscation automatically hides email addresses from bots while keeping them visible and clickable for human visitors."
}

View File

@@ -0,0 +1,25 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.zones.zones_client import zones_client
class zones_email_obfuscation_enabled(Check):
def execute(self) -> list[CheckReportCloudflare]:
findings = []
for zone in zones_client.zones:
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
email_obfuscation = (zone.settings.email_obfuscation or "").lower()
if email_obfuscation == "on":
report.status = "PASS"
report.status_extended = (
f"Email Obfuscation is enabled for zone {zone.name}."
)
else:
report.status = "FAIL"
report.status_extended = (
f"Email Obfuscation is not enabled for zone {zone.name}."
)
findings.append(report)
return findings

View File

@@ -0,0 +1,32 @@
{
"Provider": "cloudflare",
"CheckID": "zones_hsts_include_subdomains",
"CheckTitle": "HSTS includes subdomains directive for comprehensive protection",
"CheckType": [],
"ServiceName": "zones",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Zone",
"Description": "Verifies that HSTS is configured with the includeSubDomains directive to enforce HTTPS across all subdomains, preventing SSL stripping attacks on any subdomain.",
"Risk": "Without includeSubDomains, subdomains remain vulnerable to SSL stripping attacks even when the main domain has HSTS enabled, allowing attackers to intercept traffic on subdomain services.",
"RelatedUrl": "https://developers.cloudflare.com/ssl/edge-certificates/additional-options/http-strict-transport-security/",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": "resource \"cloudflare_zone_settings_override\" \"example\" {\n zone_id = var.zone_id\n settings {\n security_header {\n enabled = true\n include_subdomains = true\n max_age = 31536000\n }\n }\n}"
},
"Recommendation": {
"Text": "Enable includeSubDomains after verifying all subdomains support HTTPS. This ensures consistent security policy across the entire domain hierarchy following the principle of complete mediation.",
"Url": "https://hub.prowler.com/checks/zones_hsts_include_subdomains"
}
},
"Categories": [
"encryption"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "Ensure all subdomains are accessible via HTTPS before enabling includeSubDomains to avoid accessibility issues."
}

View File

@@ -0,0 +1,23 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.zones.zones_client import zones_client
class zones_hsts_include_subdomains(Check):
def execute(self) -> list[CheckReportCloudflare]:
findings = []
for zone in zones_client.zones:
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
if zone.settings.hsts_enabled and zone.settings.hsts_include_subdomains:
report.status = "PASS"
report.status_extended = f"HSTS is enabled with includeSubDomains directive for zone {zone.name}."
elif zone.settings.hsts_enabled:
report.status = "FAIL"
report.status_extended = f"HSTS is enabled but does not include subdomains for zone {zone.name}."
else:
report.status = "FAIL"
report.status_extended = f"HSTS is not enabled for zone {zone.name}."
findings.append(report)
return findings

View File

@@ -0,0 +1,30 @@
{
"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": "Verifies that the security level 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 the origin, increasing the attack surface and risk of successful attacks against web applications.",
"RelatedUrl": "https://developers.cloudflare.com/waf/tools/security-level/",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Set security level to Medium or High based on your threat tolerance. Use Under Attack mode during active attacks, but avoid keeping it enabled permanently as it may impact legitimate user experience.",
"Url": "https://hub.prowler.com/checks/zones_security_level"
}
},
"Categories": [],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,27 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.zones.zones_client import zones_client
class zones_security_level(Check):
def execute(self) -> list[CheckReportCloudflare]:
findings = []
acceptable_levels = ["medium", "high", "under_attack"]
for zone in zones_client.zones:
report = CheckReportCloudflare(
metadata=self.metadata(),
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:
report.status = "FAIL"
report.status_extended = (
f"Security level is set to '{security_level}' for zone {zone.name}."
)
findings.append(report)
return findings

View File

@@ -0,0 +1,32 @@
{
"Provider": "cloudflare",
"CheckID": "zones_spf_record_exists",
"CheckTitle": "SPF record exists for email authentication",
"CheckType": [],
"ServiceName": "zones",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Zone",
"Description": "Verifies that an SPF (Sender Policy Framework) TXT record exists to specify which mail servers are authorized to send email on behalf of the domain.",
"Risk": "Without SPF, attackers can forge emails appearing to come from your domain, enabling phishing attacks, brand impersonation, and reputation damage that impacts email deliverability.",
"RelatedUrl": "https://developers.cloudflare.com/dns/manage-dns-records/how-to/email-records/",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": "resource \"cloudflare_record\" \"spf\" {\n zone_id = var.zone_id\n name = \"@\"\n type = \"TXT\"\n value = \"v=spf1 include:_spf.google.com ~all\"\n}"
},
"Recommendation": {
"Text": "Configure SPF records listing authorized mail servers. Combine SPF with DKIM and DMARC for comprehensive email authentication following defense in depth principles for email security.",
"Url": "https://hub.prowler.com/checks/zones_spf_record_exists"
}
},
"Categories": [
"email-security"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": "SPF records start with 'v=spf1' and define authorized mail servers."
}

View File

@@ -0,0 +1,33 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.dns.dns_client import dns_client
from prowler.providers.cloudflare.services.zones.zones_client import zones_client
class zones_spf_record_exists(Check):
def execute(self) -> list[CheckReportCloudflare]:
findings = []
for zone in zones_client.zones:
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
# SPF records are TXT records starting with "v=spf1"
spf_records = [
record
for record in dns_client.records
if record.zone.id == zone.id
and record.type == "TXT"
and record.content.startswith("v=spf1")
]
if spf_records:
report.status = "PASS"
report.status_extended = f"SPF record exists for zone {zone.name}."
else:
report.status = "FAIL"
report.status_extended = f"No SPF record found for zone {zone.name}."
findings.append(report)
return findings

View File

@@ -0,0 +1,32 @@
{
"Provider": "cloudflare",
"CheckID": "zones_tls_1_3_enabled",
"CheckTitle": "TLS 1.3 is enabled for improved security and performance",
"CheckType": [],
"ServiceName": "zones",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "medium",
"ResourceType": "Zone",
"Description": "Verifies that TLS 1.3 is enabled to benefit from improved security through simplified cipher suites and faster handshakes with zero round-trip time resumption.",
"Risk": "Without TLS 1.3, connections use older TLS versions with more complex cipher negotiations, slower handshakes, and lack of modern security improvements that protect against downgrade attacks.",
"RelatedUrl": "https://developers.cloudflare.com/ssl/edge-certificates/additional-options/tls-13/",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable TLS 1.3 to provide the most secure and efficient TLS connections. All modern browsers support TLS 1.3, ensuring broad compatibility while improving security posture.",
"Url": "https://hub.prowler.com/checks/zones_tls_1_3_enabled"
}
},
"Categories": [
"encryption"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,21 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.zones.zones_client import zones_client
class zones_tls_1_3_enabled(Check):
def execute(self) -> list[CheckReportCloudflare]:
findings = []
for zone in zones_client.zones:
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
tls_1_3 = (zone.settings.tls_1_3 or "").lower()
if tls_1_3 in ["on", "zrt"]:
report.status = "PASS"
report.status_extended = f"TLS 1.3 is enabled for zone {zone.name}."
else:
report.status = "FAIL"
report.status_extended = f"TLS 1.3 is not enabled for zone {zone.name}."
findings.append(report)
return findings

View File

@@ -0,0 +1,32 @@
{
"Provider": "cloudflare",
"CheckID": "zones_universal_ssl_enabled",
"CheckTitle": "Universal SSL is enabled to provide SSL/TLS certificates",
"CheckType": [],
"ServiceName": "zones",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Zone",
"Description": "Verifies that Universal SSL is enabled to provide free SSL/TLS certificates for the domain and its subdomains, enabling HTTPS connections.",
"Risk": "Without Universal SSL, visitors cannot establish HTTPS connections, leaving all traffic unencrypted and vulnerable to interception. Browsers will display security warnings, degrading trust and user experience.",
"RelatedUrl": "https://developers.cloudflare.com/ssl/edge-certificates/universal-ssl/",
"Remediation": {
"Code": {
"CLI": "",
"NativeIaC": "",
"Other": "",
"Terraform": ""
},
"Recommendation": {
"Text": "Enable Universal SSL to provide HTTPS capability for your domain. This is the foundation for transport security and is required before implementing HSTS or other HTTPS-dependent features.",
"Url": "https://hub.prowler.com/checks/zones_universal_ssl_enabled"
}
},
"Categories": [
"encryption"
],
"DependsOn": [],
"RelatedTo": [],
"Notes": ""
}

View File

@@ -0,0 +1,25 @@
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.zones.zones_client import zones_client
class zones_universal_ssl_enabled(Check):
def execute(self) -> list[CheckReportCloudflare]:
findings = []
for zone in zones_client.zones:
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
universal_ssl = (zone.settings.universal_ssl or "").lower()
if universal_ssl == "on":
report.status = "PASS"
report.status_extended = (
f"Universal SSL is enabled for zone {zone.name}."
)
else:
report.status = "FAIL"
report.status_extended = (
f"Universal SSL is not enabled for zone {zone.name}."
)
findings.append(report)
return findings