- Enhanced Cloudflare Plugin with `use_builtin_ips` option to automatically use and update built-in IP ranges. - Updated Cloudflare IP restoration logic, including metadata and HAProxy config generation. - Reorganized plugin documentation with sidebar positions for improved accessibility. - Extended Cloudflare documentation to detail built-in IP ranges, examples, and configurations. - Added new test cases to validate `use_builtin_ips` functionality and file handling.
148 lines
4.9 KiB
Python
148 lines
4.9 KiB
Python
"""
|
|
Cloudflare Plugin for EasyHAProxy
|
|
|
|
This plugin restores the original visitor IP address from Cloudflare's
|
|
CF-Connecting-IP header when requests come through Cloudflare's CDN.
|
|
|
|
The plugin includes built-in Cloudflare IP ranges that are automatically
|
|
updated and written to the IP list file.
|
|
|
|
Configuration:
|
|
- ip_list_path: Path to file containing Cloudflare IP ranges (default: /etc/haproxy/cloudflare_ips.lst)
|
|
- use_builtin_ips: Use built-in Cloudflare IP ranges (default: true)
|
|
|
|
Example YAML config:
|
|
plugins:
|
|
cloudflare:
|
|
enabled: true
|
|
ip_list_path: /etc/haproxy/cloudflare_ips.lst
|
|
use_builtin_ips: true
|
|
|
|
Example Container Label:
|
|
easyhaproxy.http.plugins: "cloudflare"
|
|
|
|
HAProxy Config Generated:
|
|
# Cloudflare - Restore original visitor IP
|
|
acl from_cloudflare src -f /etc/haproxy/cloudflare_ips.lst
|
|
http-request set-header X-Forwarded-For %[req.hdr(CF-Connecting-IP)] if from_cloudflare
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
# Add parent directory to path for imports
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from plugins import PluginInterface, PluginType, PluginContext, PluginResult
|
|
from functions import loggerEasyHaproxy
|
|
|
|
|
|
class CloudflarePlugin(PluginInterface):
|
|
"""Plugin to restore original visitor IP from Cloudflare"""
|
|
|
|
# Current Cloudflare IP ranges (IPv4 and IPv6)
|
|
# Source: https://www.cloudflare.com/ips/
|
|
CLOUDFLARE_IPS = [
|
|
# IPv4
|
|
"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",
|
|
# IPv6
|
|
"2400:cb00::/32",
|
|
"2606:4700::/32",
|
|
"2803:f800::/32",
|
|
"2405:b500::/32",
|
|
"2405:8100::/32",
|
|
"2a06:98c0::/29",
|
|
"2c0f:f248::/32",
|
|
]
|
|
|
|
def __init__(self):
|
|
self.ip_list_path = "/etc/haproxy/cloudflare_ips.lst"
|
|
self.enabled = True
|
|
self.use_builtin_ips = True
|
|
|
|
@property
|
|
def name(self) -> str:
|
|
return "cloudflare"
|
|
|
|
@property
|
|
def plugin_type(self) -> PluginType:
|
|
return PluginType.DOMAIN
|
|
|
|
def configure(self, config: dict) -> None:
|
|
"""
|
|
Configure the plugin
|
|
|
|
Args:
|
|
config: Dictionary with configuration options
|
|
- ip_list_path: Path to Cloudflare IP list file
|
|
- enabled: Whether plugin is enabled
|
|
- use_builtin_ips: Use built-in Cloudflare IP ranges (default: true)
|
|
"""
|
|
if "ip_list_path" in config:
|
|
self.ip_list_path = config["ip_list_path"]
|
|
|
|
if "enabled" in config:
|
|
self.enabled = str(config["enabled"]).lower() in ["true", "1", "yes"]
|
|
|
|
if "use_builtin_ips" in config:
|
|
self.use_builtin_ips = str(config["use_builtin_ips"]).lower() in ["true", "1", "yes"]
|
|
|
|
def process(self, context: PluginContext) -> PluginResult:
|
|
"""
|
|
Generate HAProxy config to restore original IP from Cloudflare
|
|
|
|
Args:
|
|
context: Plugin execution context with domain information
|
|
|
|
Returns:
|
|
PluginResult with HAProxy configuration snippet
|
|
"""
|
|
if not self.enabled:
|
|
return PluginResult()
|
|
|
|
# Write built-in Cloudflare IPs to file if using built-in IPs
|
|
if self.use_builtin_ips:
|
|
try:
|
|
# Create directory if it doesn't exist
|
|
ip_list_dir = os.path.dirname(self.ip_list_path)
|
|
if ip_list_dir and not os.path.exists(ip_list_dir):
|
|
os.makedirs(ip_list_dir, exist_ok=True)
|
|
|
|
# Write Cloudflare IPs to file
|
|
with open(self.ip_list_path, 'w') as f:
|
|
for ip_range in self.CLOUDFLARE_IPS:
|
|
f.write(f"{ip_range}\n")
|
|
|
|
loggerEasyHaproxy.info(f"Cloudflare plugin: Written {len(self.CLOUDFLARE_IPS)} IP ranges to {self.ip_list_path}")
|
|
except Exception as e:
|
|
loggerEasyHaproxy.warning(f"Cloudflare plugin: Failed to write IP list to {self.ip_list_path}: {e}")
|
|
|
|
# Generate HAProxy config snippet
|
|
haproxy_config = f"""# Cloudflare - Restore original visitor IP
|
|
acl from_cloudflare src -f {self.ip_list_path}
|
|
http-request set-header X-Forwarded-For %[req.hdr(CF-Connecting-IP)] if from_cloudflare"""
|
|
|
|
return PluginResult(
|
|
haproxy_config=haproxy_config,
|
|
modified_easymapping=None,
|
|
metadata={
|
|
"domain": context.domain,
|
|
"ip_list_path": self.ip_list_path,
|
|
"use_builtin_ips": self.use_builtin_ips,
|
|
"ip_count": len(self.CLOUDFLARE_IPS) if self.use_builtin_ips else None
|
|
}
|
|
)
|