1
0
Fork 0

Add use_builtin_ips support and reorganize plugin documentation

- 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.
This commit is contained in:
Joao Gilberto Magalhaes 2025-12-01 18:00:52 -05:00
parent 56fc86d77d
commit b4944ac544
8 changed files with 187 additions and 11 deletions

View file

@ -4,14 +4,19 @@ 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"
@ -29,14 +34,45 @@ import sys
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:
@ -54,6 +90,7 @@ class CloudflarePlugin(PluginInterface):
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"]
@ -61,6 +98,9 @@ class CloudflarePlugin(PluginInterface):
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
@ -74,6 +114,23 @@ class CloudflarePlugin(PluginInterface):
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}
@ -84,6 +141,8 @@ http-request set-header X-Forwarded-For %[req.hdr(CF-Connecting-IP)] if from_clo
modified_easymapping=None,
metadata={
"domain": context.domain,
"ip_list_path": self.ip_list_path
"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
}
)

View file

@ -42,7 +42,9 @@ class TestCloudflarePlugin:
plugin = CloudflarePlugin()
assert plugin.name == "cloudflare"
assert plugin.enabled is True
assert plugin.use_builtin_ips is True
assert plugin.ip_list_path == "/etc/haproxy/cloudflare_ips.lst"
assert len(plugin.CLOUDFLARE_IPS) == 22 # 15 IPv4 + 7 IPv6
def test_cloudflare_plugin_configuration(self):
"""Test plugin configuration"""
@ -123,6 +125,75 @@ class TestCloudflarePlugin:
assert "acl from_cloudflare src -f /etc/haproxy/cloudflare_ips.lst" in haproxy_config
assert "http-request set-header X-Forwarded-For %[req.hdr(CF-Connecting-IP)]" in haproxy_config
def test_cloudflare_plugin_builtin_ips_enabled(self):
"""Test plugin uses built-in Cloudflare IPs and writes to file"""
# Use temp directory for testing
with tempfile.TemporaryDirectory() as tmpdir:
ip_list_path = os.path.join(tmpdir, "cloudflare_ips.lst")
plugin = CloudflarePlugin()
plugin.configure({
"use_builtin_ips": "true",
"ip_list_path": ip_list_path
})
context = PluginContext(
parsed_object={},
easymapping=[],
container_env={},
domain="example.com",
port="80",
host_config={}
)
result = plugin.process(context)
# Verify config is generated
assert result.haproxy_config is not None
assert f"acl from_cloudflare src -f {ip_list_path}" in result.haproxy_config
# Verify metadata
assert result.metadata["use_builtin_ips"] is True
assert result.metadata["ip_count"] == 22
# Verify file was written
assert os.path.exists(ip_list_path)
# Verify file contains correct number of IPs
with open(ip_list_path, 'r') as f:
lines = [line.strip() for line in f if line.strip()]
assert len(lines) == 22
# Verify some known Cloudflare IPs are in the file
assert "173.245.48.0/20" in lines
assert "2606:4700::/32" in lines
def test_cloudflare_plugin_builtin_ips_disabled(self):
"""Test plugin doesn't write to file when use_builtin_ips is disabled"""
plugin = CloudflarePlugin()
plugin.configure({
"use_builtin_ips": "false",
"ip_list_path": "/custom/cloudflare_ips.lst"
})
context = PluginContext(
parsed_object={},
easymapping=[],
container_env={},
domain="example.com",
port="80",
host_config={}
)
result = plugin.process(context)
# Verify config is generated with custom path
assert result.haproxy_config is not None
assert "acl from_cloudflare src -f /custom/cloudflare_ips.lst" in result.haproxy_config
# Verify metadata
assert result.metadata["use_builtin_ips"] is False
assert result.metadata["ip_count"] is None
class TestCleanupPlugin:
"""Test cases for CleanupPlugin (GLOBAL plugin)"""