From 71f4b9ef05dd60780e6c28dddb7fad6a69ed47b9 Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Mon, 17 Nov 2025 12:07:32 -0800 Subject: [PATCH] Add CIDR notation support for IP blocking - Update map file format to include value (IP/CIDR 1) - Fix HAProxy template to use map_ip() for CIDR support - Update runtime map commands to include value - Document CIDR range blocking in API documentation - Support blocking entire network ranges (e.g., 192.168.1.0/24) This allows blocking compromised ISP ranges and other large-scale attacks. --- IP_BLOCKING_API.md | 43 +++++++++++++++++++++++++++++--------- haproxy_manager.py | 18 +++++++++------- templates/hap_listener.tpl | 10 +++++---- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/IP_BLOCKING_API.md b/IP_BLOCKING_API.md index 4592bed..cbf10af 100644 --- a/IP_BLOCKING_API.md +++ b/IP_BLOCKING_API.md @@ -5,12 +5,23 @@ This document describes the IP blocking functionality added to HAProxy Manager, ## Overview The IP blocking feature allows administrators to: -- Block specific IP addresses from accessing any sites managed by HAProxy -- Unblock previously blocked IP addresses -- View all currently blocked IP addresses -- Track who blocked an IP and when +- Block specific IP addresses or CIDR ranges from accessing any sites managed by HAProxy +- Unblock previously blocked IP addresses or CIDR ranges +- View all currently blocked IP addresses and CIDR ranges +- Track who blocked an IP/CIDR and when -When an IP is blocked, visitors from that IP address will receive a 403 Forbidden response. +When an IP is blocked (or falls within a blocked CIDR range), visitors from that IP address will receive a 403 Forbidden response. + +### CIDR Range Support + +The IP blocking system supports CIDR notation for blocking entire network ranges: +- **Single IP**: `192.168.1.100` (blocks only this IP) +- **CIDR Range**: `192.168.1.0/24` (blocks 256 IPs from 192.168.1.0 to 192.168.1.255) +- **Common CIDR Masks**: + - `/32` - Single IP (1 address) + - `/24` - Standard subnet (256 addresses) + - `/16` - Large network (65,536 addresses) + - `/8` - Very large network (16,777,216 addresses) ## Features @@ -78,7 +89,7 @@ Add an IP address to the blocked list. ``` **Parameters:** -- `ip_address` (required): The IP address to block (e.g., "192.168.1.100") +- `ip_address` (required): The IP address or CIDR range to block (e.g., "192.168.1.100" or "192.168.1.0/24") - `reason` (optional): Reason for blocking (default: "No reason provided") - `blocked_by` (optional): Who/what initiated the block (default: "API") @@ -96,7 +107,7 @@ Add an IP address to the blocked list. - `409 Conflict`: IP address is already blocked - `500 Internal Server Error`: Configuration generation failed -**Example Request:** +**Example Request (Single IP):** ```bash curl -X POST http://localhost:8000/api/blocked-ips \ -H "Authorization: Bearer your-api-key" \ @@ -108,9 +119,21 @@ curl -X POST http://localhost:8000/api/blocked-ips \ }' ``` -### 3. Unblock an IP Address +**Example Request (CIDR Range):** +```bash +curl -X POST http://localhost:8000/api/blocked-ips \ + -H "Authorization: Bearer your-api-key" \ + -H "Content-Type: application/json" \ + -d '{ + "ip_address": "192.168.1.0/24", + "reason": "DDoS attack from compromised ISP", + "blocked_by": "WHP Security Module" + }' +``` -Remove an IP address from the blocked list. +### 3. Unblock an IP Address or CIDR Range + +Remove an IP address or CIDR range from the blocked list. **Endpoint:** `DELETE /api/blocked-ips` @@ -122,7 +145,7 @@ Remove an IP address from the blocked list. ``` **Parameters:** -- `ip_address` (required): The IP address to unblock +- `ip_address` (required): The IP address or CIDR range to unblock (must match exactly as it was blocked) **Response:** ```json diff --git a/haproxy_manager.py b/haproxy_manager.py index 0bfb519..7b44d2e 100644 --- a/haproxy_manager.py +++ b/haproxy_manager.py @@ -1340,13 +1340,15 @@ def update_blocked_ips_map(): cursor = conn.cursor() cursor.execute('SELECT ip_address FROM blocked_ips ORDER BY ip_address') blocked_ips = [row[0] for row in cursor.fetchall()] - - # Write map file + + # Write map file in HAProxy map format: + # For IP blocking, we use: 1 + # This allows map_ip() to work with both single IPs and CIDR ranges os.makedirs(os.path.dirname(BLOCKED_IPS_MAP_PATH), exist_ok=True) with open(BLOCKED_IPS_MAP_PATH, 'w') as f: for ip in blocked_ips: - f.write(f"{ip}\n") - + f.write(f"{ip} 1\n") + logger.info(f"Updated blocked IPs map file with {len(blocked_ips)} IPs") return True except Exception as e: @@ -1360,11 +1362,13 @@ def add_ip_to_runtime_map(ip_address): socket_path = HAPROXY_SOCKET_PATH else: socket_path = '/tmp/haproxy-cli' - + # Add to runtime map (map file ID 0 for blocked IPs) - cmd = f'echo "add map #0 {ip_address}" | socat stdio {socket_path}' + # Format: add map # + # For IP blocking, value is always "1" + cmd = f'echo "add map #0 {ip_address} 1" | socat stdio {socket_path}' result = subprocess.run(cmd, shell=True, capture_output=True, text=True) - + if result.returncode == 0: logger.info(f"Added IP {ip_address} to runtime map") return True diff --git a/templates/hap_listener.tpl b/templates/hap_listener.tpl index a20bedd..0c80c63 100644 --- a/templates/hap_listener.tpl +++ b/templates/hap_listener.tpl @@ -17,8 +17,10 @@ frontend web http-request set-var(txn.real_ip) src if !has_cf_connecting_ip !has_x_real_ip !has_x_forwarded_for # IP blocking using map file (manual blocks only) - # Map file: /etc/haproxy/blocked_ips.map - # Runtime updates: echo "add map #0 IP_ADDRESS" | socat stdio /var/run/haproxy.sock + # Map file format: /etc/haproxy/blocked_ips.map contains " 1" per line + # Runtime updates: echo "add map #0 IP_ADDRESS 1" | socat stdio /var/run/haproxy.sock # Checks the real client IP (from headers if present, otherwise src) - http-request set-path /blocked-ip if { var(txn.real_ip) -m ip -f /etc/haproxy/blocked_ips.map } - use_backend default-backend if { var(txn.real_ip) -m ip -f /etc/haproxy/blocked_ips.map } + # map_ip() converter supports both single IPs and CIDR ranges (e.g., 192.168.1.0/24) + acl is_blocked_ip var(txn.real_ip),map_ip(/etc/haproxy/blocked_ips.map,0) -m int gt 0 + http-request set-path /blocked-ip if is_blocked_ip + use_backend default-backend if is_blocked_ip