Add Cloudflare plugin tests with base64-encoded IP list support
- Enhanced the Cloudflare plugin by adding support for base64-encoded IP lists, prioritizing them over built-in lists. - Introduced Kubernetes integration tests for the Cloudflare IP restoration feature. - Validated plugin behavior for resource creation, pod readiness, ingress access, and HAProxy configurations. - Improved annotations in `cloudflare.yml` for Kubernetes-native compatibility. - Updated unit tests to cover IP list precedence, invalid base64 handling, and metadata verification.
This commit is contained in:
parent
90b01b1f13
commit
1fd5873dc9
4 changed files with 439 additions and 10 deletions
|
|
@ -9,6 +9,7 @@ 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)
|
||||
- ip_list: Base64-encoded list of IP ranges (one per line), takes precedence over ip_list_path
|
||||
- use_builtin_ips: Use built-in Cloudflare IP ranges (default: true)
|
||||
- update_log_format: Update HAProxy log format to show real visitor IP (default: true)
|
||||
|
||||
|
|
@ -20,6 +21,11 @@ Example YAML config:
|
|||
use_builtin_ips: true
|
||||
update_log_format: true
|
||||
|
||||
Example Kubernetes Ingress Annotation:
|
||||
easyhaproxy.plugins: "cloudflare"
|
||||
easyhaproxy.plugin.cloudflare.ip_list: "MTAuMC4wLjAvOAoxNzIuMTYuMC4wLzEyCjE5Mi4xNjguMC4wLzE2Cg=="
|
||||
easyhaproxy.plugin.cloudflare.update_log_format: "true"
|
||||
|
||||
Example Container Label:
|
||||
easyhaproxy.http.plugins: "cloudflare"
|
||||
easyhaproxy.http.plugin.cloudflare.update_log_format: "true"
|
||||
|
|
@ -35,6 +41,7 @@ Log Format (when update_log_format=true):
|
|||
Format: real_ip/connection_ip [timestamp] request status bytes ...
|
||||
"""
|
||||
|
||||
import base64
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
|
@ -82,6 +89,7 @@ class CloudflarePlugin(PluginInterface):
|
|||
self.enabled = True
|
||||
self.use_builtin_ips = True
|
||||
self.update_log_format = True
|
||||
self.ip_list = None
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
|
|
@ -98,6 +106,7 @@ class CloudflarePlugin(PluginInterface):
|
|||
Args:
|
||||
config: Dictionary with configuration options
|
||||
- ip_list_path: Path to Cloudflare IP list file
|
||||
- ip_list: Base64-encoded list of IP ranges (one per line)
|
||||
- enabled: Whether plugin is enabled
|
||||
- use_builtin_ips: Use built-in Cloudflare IP ranges (default: true)
|
||||
- update_log_format: Update HAProxy log format to show real IP (default: true)
|
||||
|
|
@ -105,6 +114,14 @@ class CloudflarePlugin(PluginInterface):
|
|||
if "ip_list_path" in config:
|
||||
self.ip_list_path = config["ip_list_path"]
|
||||
|
||||
if "ip_list" in config:
|
||||
# Decode from base64 (consistent with JWT validator pubkey parameter)
|
||||
try:
|
||||
self.ip_list = base64.b64decode(config["ip_list"]).decode('utf-8')
|
||||
except Exception as e:
|
||||
logger_easyhaproxy.warning(f"Cloudflare plugin: Failed to decode ip_list: {e}")
|
||||
self.ip_list = None
|
||||
|
||||
if "enabled" in config:
|
||||
self.enabled = str(config["enabled"]).lower() in ["true", "1", "yes"]
|
||||
|
||||
|
|
@ -127,22 +144,41 @@ 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:
|
||||
# Determine which IPs to write to file
|
||||
ips_to_write = None
|
||||
ip_source = None
|
||||
|
||||
if self.ip_list:
|
||||
# Priority 1: Base64-encoded ip_list from annotation
|
||||
ip_lines = [line.strip() for line in self.ip_list.split('\n') if line.strip()]
|
||||
ips_to_write = ip_lines
|
||||
ip_source = "base64 ip_list"
|
||||
elif self.use_builtin_ips:
|
||||
# Priority 2: Built-in Cloudflare IPs
|
||||
ips_to_write = self.CLOUDFLARE_IPS
|
||||
ip_source = "built-in IPs"
|
||||
|
||||
# Write IPs to file if we have any
|
||||
if ips_to_write:
|
||||
try:
|
||||
# Create directory if it doesn't exist
|
||||
# Create directory if needed
|
||||
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
|
||||
# Write IPs to file
|
||||
with open(self.ip_list_path, 'w') as f:
|
||||
for ip_range in self.CLOUDFLARE_IPS:
|
||||
for ip_range in ips_to_write:
|
||||
f.write(f"{ip_range}\n")
|
||||
|
||||
logger_easyhaproxy.info(f"Cloudflare plugin: Written {len(self.CLOUDFLARE_IPS)} IP ranges to {self.ip_list_path}")
|
||||
logger_easyhaproxy.info(
|
||||
f"Cloudflare plugin: Written {len(ips_to_write)} IP ranges "
|
||||
f"from {ip_source} to {self.ip_list_path}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger_easyhaproxy.warning(f"Cloudflare plugin: Failed to write IP list to {self.ip_list_path}: {e}")
|
||||
logger_easyhaproxy.warning(
|
||||
f"Cloudflare plugin: Failed to write IP list to {self.ip_list_path}: {e}"
|
||||
)
|
||||
|
||||
# Generate HAProxy config snippet for backend
|
||||
haproxy_config = f"""# Cloudflare - Restore original visitor IP
|
||||
|
|
@ -164,9 +200,11 @@ log-format "%{+Q}[var(txn.real_ip)]:-/%ci:%cp [%tr] %ft %b/%s %TR/%Tw/%Tc/%Tr/%T
|
|||
metadata={
|
||||
"domain": context.domain,
|
||||
"ip_list_path": self.ip_list_path,
|
||||
"ip_list_provided": self.ip_list is not None,
|
||||
"use_builtin_ips": self.use_builtin_ips,
|
||||
"update_log_format": self.update_log_format,
|
||||
"defaults_config": log_format_config,
|
||||
"ip_count": len(self.CLOUDFLARE_IPS) if self.use_builtin_ips else None
|
||||
"ip_count": len(ips_to_write) if ips_to_write else None,
|
||||
"ip_source": ip_source if ips_to_write else "existing file"
|
||||
}
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue