Merge branch 'PROWLER-386-add-cloudflare-provider-to-cli' into cloudflare-pr2-tls-email-checks

This commit is contained in:
HugoPBrito
2026-01-12 17:25:48 +01:00
5 changed files with 99 additions and 19 deletions

View File

@@ -1,14 +1,14 @@
{
"Provider": "cloudflare",
"CheckID": "zones_ssl_strict",
"CheckTitle": "SSL/TLS encryption mode is set to Full, or Full Strict",
"CheckTitle": "SSL/TLS encryption mode is set to Full (Strict)",
"CheckType": [],
"ServiceName": "zones",
"SubServiceName": "",
"ResourceIdTemplate": "",
"Severity": "high",
"ResourceType": "Zone",
"Description": "**Cloudflare zones** are assessed for **SSL/TLS encryption mode** by checking if the mode is set to `Full` to ensure **end-to-end encryption** with certificate validation.",
"Description": "**Cloudflare zones** are assessed for **SSL/TLS encryption mode** by checking if the mode is set to `Full (Strict)` to ensure **end-to-end encryption** with certificate validation.",
"Risk": "Without **strict SSL mode**, traffic between Cloudflare and origin may use unvalidated or unencrypted connections.\n- **Confidentiality**: sensitive data can be intercepted in transit via man-in-the-middle attacks\n- **Integrity**: responses can be modified without detection between Cloudflare and origin\n- **Compliance**: may violate PCI-DSS, HIPAA, and other regulatory requirements for encrypted transport",
"RelatedUrl": "",
"AdditionalURLs": [

View File

@@ -3,10 +3,10 @@ from prowler.providers.cloudflare.services.zones.zones_client import zones_clien
class zones_ssl_strict(Check):
"""Ensure that SSL/TLS encryption mode is set to strict for Cloudflare zones.
"""Ensure that SSL/TLS encryption mode is set to Full (Strict) for Cloudflare zones.
The SSL/TLS encryption mode determines how Cloudflare connects to the origin
server. In 'strict' or 'full' mode, Cloudflare validates the origin
server. In 'strict' mode, Cloudflare validates the origin
server's SSL certificate, ensuring end-to-end encryption with certificate
verification. Lower modes (off, flexible, full) are vulnerable to
man-in-the-middle attacks between Cloudflare and the origin.
@@ -16,13 +16,13 @@ class zones_ssl_strict(Check):
"""Execute the SSL strict mode check.
Iterates through all Cloudflare zones and verifies that the SSL/TLS
encryption mode is set to 'strict' or 'full'. These modes
require a valid SSL certificate on the origin server and provide
encryption mode is set to 'strict'. This mode
requires a valid SSL certificate on the origin server and provides
full end-to-end encryption with certificate validation.
Returns:
A list of CheckReportCloudflare objects with PASS status if
SSL mode is 'strict' or 'full', or FAIL status if using
SSL mode is 'strict', or FAIL status if using
less secure modes like 'off', 'flexible', or 'full'.
"""
findings = []
@@ -32,11 +32,11 @@ class zones_ssl_strict(Check):
resource=zone,
)
ssl_mode = (zone.settings.ssl_encryption_mode or "").lower()
if ssl_mode in ["strict", "full"]:
if ssl_mode == "strict":
report.status = "PASS"
report.status_extended = f"SSL/TLS encryption mode is set to {ssl_mode} for zone {zone.name}."
report.status_extended = f"SSL/TLS encryption mode is set to Full (Strict) for zone {zone.name}."
else:
report.status = "FAIL"
report.status_extended = f"SSL/TLS encryption mode is set to {ssl_mode} for zone {zone.name}, which is not strict or full."
report.status_extended = f"SSL/TLS encryption mode is set to {ssl_mode.capitalize()} for zone {zone.name}, which is not Full (Strict)."
findings.append(report)
return findings

View File

@@ -70,7 +70,7 @@ class Test_zones_ssl_strict:
assert result[0].status == "PASS"
assert (
result[0].status_extended
== f"SSL/TLS encryption mode is set to strict for zone {ZONE_NAME}."
== f"SSL/TLS encryption mode is set to Full (Strict) for zone {ZONE_NAME}."
)
def test_zone_ssl_full_mode(self):
@@ -104,10 +104,10 @@ class Test_zones_ssl_strict:
check = zones_ssl_strict()
result = check.execute()
assert len(result) == 1
assert result[0].status == "PASS"
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"SSL/TLS encryption mode is set to full for zone {ZONE_NAME}."
== f"SSL/TLS encryption mode is set to Full for zone {ZONE_NAME}, which is not Full (Strict)."
)
def test_zone_ssl_flexible_mode(self):
@@ -144,7 +144,7 @@ class Test_zones_ssl_strict:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"SSL/TLS encryption mode is set to flexible for zone {ZONE_NAME}, which is not strict or full."
== f"SSL/TLS encryption mode is set to Flexible for zone {ZONE_NAME}, which is not Full (Strict)."
)
def test_zone_ssl_off_mode(self):
@@ -181,5 +181,5 @@ class Test_zones_ssl_strict:
assert result[0].status == "FAIL"
assert (
result[0].status_extended
== f"SSL/TLS encryption mode is set to off for zone {ZONE_NAME}, which is not strict or full."
== f"SSL/TLS encryption mode is set to Off for zone {ZONE_NAME}, which is not Full (Strict)."
)

View File

@@ -48,6 +48,88 @@ export const getProviders = async ({
}
};
/**
* Fetches all providers by iterating through all pages.
* This is useful when you need the complete list of providers without pagination limits,
* such as for dropdown menus or selection lists.
*/
export const getAllProviders = async ({
query = "",
sort = "",
filters = {},
}: {
query?: string;
sort?: string;
filters?: Record<string, unknown>;
} = {}): Promise<ProvidersApiResponse | undefined> => {
const headers = await getAuthHeaders({ contentType: false });
const pageSize = 100; // Use larger page size to minimize API calls
let currentPage = 1;
let allProviders: ProvidersApiResponse["data"] = [];
let lastResponse: ProvidersApiResponse | undefined;
let hasMorePages = true;
try {
while (hasMorePages) {
const url = new URL(`${apiBaseUrl}/providers?include=provider_groups`);
url.searchParams.append("page[number]", currentPage.toString());
url.searchParams.append("page[size]", pageSize.toString());
if (query) url.searchParams.append("filter[search]", query);
if (sort) url.searchParams.append("sort", sort);
Object.entries(filters).forEach(([key, value]) => {
if (key !== "filter[search]") {
url.searchParams.append(key, String(value));
}
});
const response = await fetch(url.toString(), { headers });
const data = (await handleApiResponse(response)) as
| ProvidersApiResponse
| undefined;
if (!data?.data || data.data.length === 0) {
hasMorePages = false;
continue;
}
allProviders = [...allProviders, ...data.data];
lastResponse = data;
// Check if we've fetched all pages
const totalPages = data.meta?.pagination?.pages || 1;
if (currentPage >= totalPages) {
hasMorePages = false;
} else {
currentPage++;
}
}
// Return combined response with all providers
if (lastResponse) {
return {
...lastResponse,
data: allProviders,
meta: {
...lastResponse.meta,
pagination: {
...lastResponse.meta?.pagination,
page: 1,
pages: 1,
count: allProviders.length,
},
},
};
}
return undefined;
} catch (error) {
console.error("Error fetching all providers:", error);
return undefined;
}
};
export const getProvider = async (formData: FormData) => {
const headers = await getAuthHeaders({ contentType: false });
const providerId = formData.get("id");

View File

@@ -1,7 +1,7 @@
import { Spacer } from "@heroui/spacer";
import { Suspense } from "react";
import { getProviders } from "@/actions/providers";
import { getAllProviders } from "@/actions/providers";
import { getScans, getScansByState } from "@/actions/scans";
import { auth } from "@/auth.config";
import { MutedFindingsConfigButton } from "@/components/providers";
@@ -33,9 +33,7 @@ export default async function Scans({
const filteredParams = { ...resolvedSearchParams };
delete filteredParams.scanId;
const providersData = await getProviders({
pageSize: 50,
});
const providersData = await getAllProviders();
const providerInfo =
providersData?.data