From 88e862544bcbfdec122d16d685772634209973db Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Sun, 1 Jun 2025 23:03:00 +0200 Subject: [PATCH 1/4] fix[domains]: Add CDN provider detection with dynamic display names Implements generic CDN detection service supporting Cloudflare, Fastly, and Bunny CDN. Replaces hardcoded "Behind Cloudflare" text with dynamic provider names and adds IP range validation for comprehensive CDN detection. --- .../application/domains/show-domains.tsx | 3 +- packages/server/src/services/cdn.ts | 633 ++++++++++++++++++ packages/server/src/services/domain.ts | 39 +- 3 files changed, 645 insertions(+), 30 deletions(-) create mode 100644 packages/server/src/services/cdn.ts diff --git a/apps/dokploy/components/dashboard/application/domains/show-domains.tsx b/apps/dokploy/components/dashboard/application/domains/show-domains.tsx index fe637353..97ac6ac4 100644 --- a/apps/dokploy/components/dashboard/application/domains/show-domains.tsx +++ b/apps/dokploy/components/dashboard/application/domains/show-domains.tsx @@ -119,6 +119,7 @@ export const ShowDomains = ({ id, type }: Props) => { isValid: result.isValid, error: result.error, resolvedIp: result.resolvedIp, + cdnProvider: result.cdnProvider, message: result.error && result.isValid ? result.error : undefined, }, })); @@ -355,7 +356,7 @@ export const ShowDomains = ({ id, type }: Props) => { <> {validationState.message - ? "Behind Cloudflare" + ? `Behind ${validationState.cdnProvider}` : "DNS Valid"} ) : validationState?.error ? ( diff --git a/packages/server/src/services/cdn.ts b/packages/server/src/services/cdn.ts new file mode 100644 index 00000000..66549ce9 --- /dev/null +++ b/packages/server/src/services/cdn.ts @@ -0,0 +1,633 @@ +// CDN Provider Interface +export interface CDNProvider { + name: string; + displayName: string; + checkIp: (ip: string) => boolean; + warningMessage: string; +} + +const isIPInCIDR = (ip: string, cidr: string): boolean => { + const [network, prefixLength] = cidr.split("/"); + const prefix = Number.parseInt(prefixLength, 10); + + // Convert IP addresses to 32-bit integers + const ipToInt = (ipStr: string): number => { + return ( + ipStr + .split(".") + .reduce((acc, octet) => (acc << 8) + Number.parseInt(octet, 10), 0) >>> + 0 + ); + }; + + const ipInt = ipToInt(ip); + const networkInt = ipToInt(network); + const mask = (0xffffffff << (32 - prefix)) >>> 0; + + return (ipInt & mask) === (networkInt & mask); +}; + +// Cloudflare IP ranges +// https://www.cloudflare.com/ips-v4 +const CLOUDFLARE_IP_RANGES = [ + "173.245.48.0/20", + "103.21.244.0/22", + "103.22.200.0/22", + "103.31.4.0/22", + "141.101.64.0/18", + "108.162.192.0/18", + "190.93.240.0/20", + "188.114.96.0/20", + "197.234.240.0/22", + "198.41.128.0/17", + "162.158.0.0/15", + "104.16.0.0/13", + "104.24.0.0/14", + "172.64.0.0/13", + "131.0.72.0/22", +]; + +// Fastly IP ranges +// https://api.fastly.com/public-ip-list +const FASTLY_IP_RANGES = [ + "23.235.32.0/20", + "43.249.72.0/22", + "103.244.50.0/24", + "103.245.222.0/23", + "103.245.224.0/24", + "104.156.80.0/20", + "140.248.64.0/18", + "140.248.128.0/17", + "146.75.0.0/17", + "151.101.0.0/16", + "157.52.64.0/18", + "167.82.0.0/17", + "167.82.128.0/20", + "167.82.160.0/20", + "167.82.224.0/20", + "172.111.64.0/18", + "185.31.16.0/22", + "199.27.72.0/21", + "199.232.0.0/16", +]; + +// Bunny CDN IP addresses +// https://bunnycdn.com/api/system/edgeserverlist +const BUNNY_CDN_IPS = new Set([ + "89.187.188.227", + "89.187.188.228", + "139.180.134.196", + "89.38.96.158", + "89.187.162.249", + "89.187.162.242", + "185.102.217.65", + "185.93.1.243", + "156.146.40.49", + "185.59.220.199", + "185.59.220.198", + "195.181.166.158", + "185.180.12.68", + "138.199.24.209", + "138.199.24.211", + "79.127.216.111", + "79.127.216.112", + "89.187.169.47", + "138.199.24.218", + "185.40.106.117", + "200.25.45.4", + "200.25.57.5", + "193.162.131.1", + "200.25.11.8", + "200.25.53.5", + "200.25.13.98", + "41.242.2.18", + "200.25.62.5", + "200.25.38.69", + "200.25.42.70", + "200.25.36.166", + "195.206.229.106", + "194.242.11.186", + "185.164.35.8", + "94.20.154.22", + "185.93.1.244", + "156.59.145.154", + "143.244.49.177", + "138.199.46.66", + "138.199.37.227", + "138.199.37.231", + "138.199.37.230", + "138.199.37.229", + "138.199.46.69", + "138.199.46.68", + "138.199.46.67", + "185.93.1.246", + "138.199.37.232", + "195.181.163.196", + "107.182.163.162", + "195.181.163.195", + "84.17.46.53", + "212.102.40.114", + "84.17.46.54", + "138.199.40.58", + "143.244.38.134", + "143.244.38.136", + "185.152.64.17", + "84.17.59.115", + "89.187.165.194", + "138.199.15.193", + "89.35.237.170", + "37.19.216.130", + "185.93.1.247", + "185.93.3.244", + "143.244.49.179", + "143.244.49.180", + "138.199.9.104", + "185.152.66.243", + "143.244.49.178", + "169.150.221.147", + "200.25.18.73", + "84.17.63.178", + "200.25.32.131", + "37.19.207.34", + "192.189.65.146", + "143.244.45.177", + "185.93.1.249", + "185.93.1.250", + "169.150.215.115", + "209.177.87.197", + "156.146.56.162", + "156.146.56.161", + "185.93.2.246", + "185.93.2.245", + "212.102.40.113", + "185.93.2.244", + "143.244.50.82", + "143.244.50.83", + "156.146.56.163", + "129.227.217.178", + "129.227.217.179", + "200.25.69.94", + "128.1.52.179", + "200.25.16.103", + "15.235.54.226", + "102.67.138.155", + "156.146.43.65", + "195.181.163.203", + "195.181.163.202", + "156.146.56.169", + "156.146.56.170", + "156.146.56.166", + "156.146.56.171", + "169.150.207.210", + "156.146.56.167", + "143.244.50.84", + "143.244.50.85", + "143.244.50.86", + "143.244.50.87", + "156.146.56.168", + "169.150.207.211", + "212.102.50.59", + "146.185.248.15", + "143.244.50.90", + "143.244.50.91", + "143.244.50.88", + "143.244.50.209", + "143.244.50.213", + "143.244.50.214", + "143.244.49.183", + "143.244.50.89", + "143.244.50.210", + "143.244.50.211", + "143.244.50.212", + "5.42.206.66", + "94.46.27.186", + "169.150.207.213", + "169.150.207.214", + "169.150.207.215", + "169.150.207.212", + "169.150.219.114", + "169.150.202.210", + "169.150.242.193", + "185.93.1.251", + "169.150.207.216", + "169.150.207.217", + "169.150.238.19", + "102.219.126.20", + "156.59.66.182", + "122.10.251.130", + "185.24.11.18", + "138.199.36.7", + "138.199.36.8", + "138.199.36.9", + "138.199.36.10", + "138.199.36.11", + "138.199.37.225", + "84.17.46.49", + "84.17.37.217", + "169.150.225.35", + "169.150.225.36", + "169.150.225.37", + "169.150.225.38", + "169.150.225.39", + "169.150.225.34", + "169.150.236.97", + "169.150.236.98", + "169.150.236.99", + "169.150.236.100", + "93.189.63.149", + "143.244.56.49", + "143.244.56.50", + "143.244.56.51", + "169.150.247.40", + "169.150.247.33", + "169.150.247.34", + "169.150.247.35", + "169.150.247.36", + "169.150.247.37", + "169.150.247.38", + "169.150.247.39", + "38.142.94.218", + "87.249.137.52", + "38.104.169.186", + "66.181.163.74", + "84.17.38.227", + "84.17.38.228", + "84.17.38.229", + "84.17.38.230", + "84.17.38.231", + "84.17.38.232", + "169.150.225.41", + "169.150.225.42", + "169.150.249.162", + "169.150.249.163", + "169.150.249.164", + "169.150.249.165", + "169.150.249.166", + "169.150.249.167", + "169.150.249.168", + "169.150.249.169", + "185.131.64.124", + "103.112.0.22", + "37.236.234.2", + "169.150.252.209", + "212.102.46.118", + "192.169.120.162", + "93.180.217.214", + "37.19.203.178", + "107.155.47.146", + "193.201.190.174", + "156.59.95.218", + "213.170.143.139", + "129.227.186.154", + "195.238.127.98", + "200.25.22.6", + "204.16.244.92", + "200.25.70.101", + "200.25.66.100", + "139.180.209.182", + "103.108.231.41", + "103.108.229.5", + "103.216.220.9", + "169.150.225.40", + "212.102.50.49", + "212.102.50.52", + "109.61.83.242", + "109.61.83.243", + "212.102.50.50", + "169.150.225.43", + "45.125.247.57", + "103.235.199.170", + "128.1.35.170", + "38.32.110.58", + "169.150.220.228", + "169.150.220.229", + "169.150.220.230", + "169.150.220.231", + "138.199.4.179", + "207.211.214.145", + "109.61.86.193", + "103.214.20.95", + "178.175.134.51", + "138.199.4.178", + "172.255.253.140", + "185.24.11.19", + "109.61.83.244", + "109.61.83.245", + "84.17.38.250", + "84.17.38.251", + "146.59.69.202", + "146.70.80.218", + "200.25.80.74", + "79.127.213.214", + "79.127.213.215", + "79.127.213.216", + "79.127.213.217", + "195.69.140.112", + "109.61.83.247", + "109.61.83.246", + "185.93.2.248", + "109.61.83.249", + "109.61.83.250", + "109.61.83.251", + "46.199.75.115", + "141.164.35.160", + "109.61.83.97", + "109.61.83.98", + "109.61.83.99", + "129.227.179.18", + "185.180.14.250", + "152.89.160.26", + "5.189.202.62", + "98.98.242.142", + "156.59.92.126", + "84.17.59.117", + "79.127.216.66", + "79.127.204.113", + "79.127.237.132", + "169.150.236.104", + "169.150.236.105", + "37.27.135.61", + "158.51.123.205", + "156.146.43.70", + "156.146.43.71", + "156.146.43.72", + "180.149.231.175", + "185.93.2.243", + "143.244.56.52", + "143.244.56.53", + "143.244.56.54", + "143.244.56.55", + "143.244.56.56", + "143.244.56.57", + "143.244.56.58", + "144.76.236.44", + "88.198.57.50", + "78.46.69.199", + "136.243.16.49", + "138.201.86.122", + "136.243.42.90", + "88.99.95.221", + "178.63.2.112", + "5.9.98.45", + "136.243.42.10", + "169.150.236.106", + "169.150.236.107", + "185.93.1.242", + "185.93.1.245", + "143.244.60.193", + "195.181.163.194", + "79.127.188.193", + "79.127.188.196", + "79.127.188.194", + "79.127.188.195", + "104.166.144.106", + "156.59.126.78", + "185.135.85.154", + "38.54.5.37", + "38.54.3.92", + "185.165.170.74", + "207.121.80.118", + "207.121.46.228", + "207.121.46.236", + "207.121.46.244", + "207.121.46.252", + "216.202.235.164", + "207.121.46.220", + "207.121.75.132", + "207.121.80.12", + "207.121.80.172", + "207.121.90.60", + "207.121.90.68", + "207.121.97.204", + "207.121.90.252", + "207.121.97.236", + "207.121.99.12", + "138.199.24.219", + "185.93.2.251", + "138.199.46.65", + "207.121.41.196", + "207.121.99.20", + "207.121.99.36", + "207.121.99.44", + "207.121.99.52", + "207.121.99.60", + "207.121.23.68", + "207.121.23.124", + "207.121.23.244", + "207.121.23.180", + "207.121.23.188", + "207.121.23.196", + "207.121.23.204", + "207.121.24.52", + "207.121.24.60", + "207.121.24.68", + "207.121.24.76", + "207.121.24.92", + "207.121.24.100", + "207.121.24.108", + "207.121.24.116", + "154.95.86.76", + "5.9.99.73", + "78.46.92.118", + "144.76.65.213", + "78.46.156.89", + "88.198.9.155", + "144.76.79.22", + "103.1.215.93", + "103.137.12.33", + "103.107.196.31", + "116.90.72.155", + "103.137.14.5", + "116.90.75.65", + "37.19.207.37", + "208.83.234.224", + "116.202.155.146", + "116.202.193.178", + "116.202.224.168", + "188.40.126.227", + "88.99.26.189", + "168.119.39.238", + "88.99.26.97", + "168.119.12.188", + "176.9.139.55", + "142.132.223.79", + "142.132.223.80", + "142.132.223.81", + "46.4.116.17", + "46.4.119.81", + "167.235.114.167", + "159.69.68.171", + "178.63.21.52", + "46.4.120.152", + "116.202.80.247", + "5.9.71.119", + "195.201.11.156", + "78.46.123.17", + "46.4.113.143", + "136.243.2.236", + "195.201.81.217", + "148.251.42.123", + "94.130.68.122", + "88.198.22.103", + "46.4.102.90", + "157.90.180.205", + "162.55.135.11", + "195.201.109.59", + "148.251.41.244", + "116.202.235.16", + "128.140.70.141", + "78.46.74.86", + "78.46.74.85", + "178.63.41.242", + "178.63.41.247", + "178.63.41.234", + "104.237.53.74", + "104.237.54.154", + "104.237.51.58", + "64.185.235.90", + "64.185.234.114", + "64.185.232.194", + "64.185.232.178", + "64.185.232.82", + "103.60.15.169", + "103.60.15.170", + "103.60.15.171", + "103.60.15.172", + "103.60.15.173", + "103.60.15.162", + "103.60.15.163", + "103.60.15.164", + "103.60.15.165", + "103.60.15.166", + "103.60.15.167", + "103.60.15.168", + "109.248.43.116", + "109.248.43.117", + "109.248.43.162", + "109.248.43.163", + "109.248.43.164", + "109.248.43.165", + "49.12.71.27", + "49.12.0.158", + "78.47.94.156", + "109.248.43.159", + "109.248.43.160", + "109.248.43.208", + "109.248.43.179", + "109.248.43.232", + "109.248.43.231", + "109.248.43.241", + "109.248.43.236", + "109.248.43.240", + "109.248.43.103", + "116.202.118.194", + "116.202.80.29", + "159.69.57.80", + "139.180.129.216", + "139.99.174.7", + "89.187.169.18", + "89.187.179.7", + "143.244.62.213", + "185.93.3.246", + "195.181.163.198", + "185.152.64.19", + "84.17.37.211", + "212.102.50.54", + "212.102.46.115", + "143.244.38.135", + "169.150.238.21", + "169.150.207.51", + "169.150.207.49", + "84.17.38.226", + "84.17.38.225", + "37.19.222.248", + "37.19.222.249", + "169.150.247.139", + "169.150.247.177", + "169.150.247.178", + "169.150.213.49", + "212.102.46.119", + "84.17.38.234", + "84.17.38.233", + "169.150.247.179", + "169.150.247.180", + "169.150.247.181", + "169.150.247.182", + "169.150.247.183", + "169.150.247.138", + "169.150.247.184", + "169.150.247.185", + "156.146.58.83", + "212.102.43.88", + "89.187.169.26", + "109.61.89.57", + "109.61.89.58", + "109.61.83.241", + "84.17.38.243", + "84.17.38.244", + "84.17.38.246", + "84.17.38.247", + "84.17.38.245", + "143.244.38.129", + "84.17.38.248", + "89.187.176.34", + "185.152.64.23", + "79.127.213.209", + "79.127.213.210", + "84.17.37.209", + "156.146.43.68", + "185.93.3.243", + "79.127.219.198", + "138.199.33.57", + "79.127.242.89", + "138.199.4.136", + "169.150.220.235", + "138.199.4.129", + "138.199.4.177", + "37.19.222.34", + "46.151.193.85", + "212.104.158.17", + "212.104.158.18", + "212.104.158.19", + "212.104.158.20", + "212.104.158.21", + "212.104.158.22", + "212.104.158.24", + "212.104.158.26", + "79.127.237.134", + "89.187.184.177", + "89.187.184.179", + "89.187.184.173", + "89.187.184.178", + "89.187.184.176", +]); + +const CDN_PROVIDERS: CDNProvider[] = [ + { + name: "cloudflare", + displayName: "Cloudflare", + checkIp: (ip: string) => + CLOUDFLARE_IP_RANGES.some((range) => isIPInCIDR(ip, range)), + warningMessage: + "Domain is behind Cloudflare - actual IP is masked by Cloudflare proxy", + }, + { + name: "bunnycdn", + displayName: "Bunny CDN", + checkIp: (ip: string) => BUNNY_CDN_IPS.has(ip), + warningMessage: + "Domain is behind Bunny CDN - actual IP is masked by CDN proxy", + }, + { + name: "fastly", + displayName: "Fastly", + checkIp: (ip: string) => + FASTLY_IP_RANGES.some((range) => isIPInCIDR(ip, range)), + warningMessage: + "Domain is behind Fastly - actual IP is masked by CDN proxy", + }, +]; + +export const detectCDNProvider = (ip: string): CDNProvider | null => { + return CDN_PROVIDERS.find((provider) => provider.checkIp(ip)) || null; +}; diff --git a/packages/server/src/services/domain.ts b/packages/server/src/services/domain.ts index edbf3cca..f81fffb9 100644 --- a/packages/server/src/services/domain.ts +++ b/packages/server/src/services/domain.ts @@ -8,6 +8,7 @@ import { eq } from "drizzle-orm"; import { type apiCreateDomain, domains } from "../db/schema"; import { findUserById } from "./admin"; import { findApplicationById } from "./application"; +import { detectCDNProvider } from "./cdn"; import { findServerById } from "./server"; export type Domain = typeof domains.$inferSelect; @@ -142,28 +143,6 @@ export const getDomainHost = (domain: Domain) => { const resolveDns = promisify(dns.resolve4); -// Cloudflare IP ranges (simplified - these are some common ones) -const CLOUDFLARE_IPS = [ - "172.67.", - "104.21.", - "104.16.", - "104.17.", - "104.18.", - "104.19.", - "104.20.", - "104.22.", - "104.23.", - "104.24.", - "104.25.", - "104.26.", - "104.27.", - "104.28.", -]; - -const isCloudflareIp = (ip: string) => { - return CLOUDFLARE_IPS.some((range) => ip.startsWith(range)); -}; - export const validateDomain = async ( domain: string, expectedIp?: string, @@ -172,6 +151,7 @@ export const validateDomain = async ( resolvedIp?: string; error?: string; isCloudflare?: boolean; + cdnProvider?: string; }> => { try { // Remove protocol and path if present @@ -182,17 +162,18 @@ export const validateDomain = async ( const resolvedIps = ips.map((ip) => ip.toString()); - // Check if it's a Cloudflare IP - const behindCloudflare = ips.some((ip) => isCloudflareIp(ip)); + // Check if any IP belongs to a CDN provider + const cdnProvider = ips + .map((ip) => detectCDNProvider(ip)) + .find((provider) => provider !== null); - // If behind Cloudflare, we consider it valid but inform the user - if (behindCloudflare) { + // If behind a CDN, we consider it valid but inform the user + if (cdnProvider) { return { isValid: true, resolvedIp: resolvedIps.join(", "), - isCloudflare: true, - error: - "Domain is behind Cloudflare - actual IP is masked by Cloudflare proxy", + cdnProvider: cdnProvider.displayName, + error: cdnProvider.warningMessage, }; } From e8b3abb7c973d58f3a2dce31bd5fe2fce0fc84b9 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Mon, 2 Jun 2025 12:03:14 +0200 Subject: [PATCH 2/4] fix: Add validation for CIDR format in isIPInCIDR function --- packages/server/src/services/cdn.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/src/services/cdn.ts b/packages/server/src/services/cdn.ts index 66549ce9..9d4d865c 100644 --- a/packages/server/src/services/cdn.ts +++ b/packages/server/src/services/cdn.ts @@ -8,6 +8,7 @@ export interface CDNProvider { const isIPInCIDR = (ip: string, cidr: string): boolean => { const [network, prefixLength] = cidr.split("/"); + if (!network || !prefixLength) return false; const prefix = Number.parseInt(prefixLength, 10); // Convert IP addresses to 32-bit integers From 9a1f0b467d172f9ec5e3407be0196c4d024d840e Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Mon, 2 Jun 2025 12:07:18 +0200 Subject: [PATCH 3/4] fix: domain validation message display logic Check for both message and cdnProvider before showing CDN status to prevent displaying "Behind undefined" when cdnProvider is missing. --- .../components/dashboard/application/domains/show-domains.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/application/domains/show-domains.tsx b/apps/dokploy/components/dashboard/application/domains/show-domains.tsx index 97ac6ac4..ba2a471b 100644 --- a/apps/dokploy/components/dashboard/application/domains/show-domains.tsx +++ b/apps/dokploy/components/dashboard/application/domains/show-domains.tsx @@ -39,6 +39,7 @@ export type ValidationState = { error?: string; resolvedIp?: string; message?: string; + cdnProvider?: string; }; export type ValidationStates = Record; @@ -355,7 +356,7 @@ export const ShowDomains = ({ id, type }: Props) => { ) : validationState?.isValid ? ( <> - {validationState.message + {validationState.message && validationState.cdnProvider ? `Behind ${validationState.cdnProvider}` : "DNS Valid"} From a659594134ab4dbf1b7b814ae9b68a3d673c90f6 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 10:07:40 +0000 Subject: [PATCH 4/4] [autofix.ci] apply automated fixes --- .../components/dashboard/application/domains/show-domains.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/dokploy/components/dashboard/application/domains/show-domains.tsx b/apps/dokploy/components/dashboard/application/domains/show-domains.tsx index ba2a471b..7bb58dfb 100644 --- a/apps/dokploy/components/dashboard/application/domains/show-domains.tsx +++ b/apps/dokploy/components/dashboard/application/domains/show-domains.tsx @@ -356,7 +356,8 @@ export const ShowDomains = ({ id, type }: Props) => { ) : validationState?.isValid ? ( <> - {validationState.message && validationState.cdnProvider + {validationState.message && + validationState.cdnProvider ? `Behind ${validationState.cdnProvider}` : "DNS Valid"}