From 88e862544bcbfdec122d16d685772634209973db Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Sun, 1 Jun 2025 23:03:00 +0200 Subject: [PATCH] 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, }; }