fix(mitmproxy): Fix wildcard route priority matching

- Support both "*.domain" and ".domain" wildcard formats in haproxy_router.py
- Sort wildcards by length (longest first) for correct specificity matching
- Add auto-reload: check routes file mtime every 10 requests
- Update metablogizerctl to use mitmproxyctl sync-routes

Also fix luci-app-wazuh api.js to use baseclass.extend

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-02-14 16:03:43 +01:00
parent b1c34021db
commit 9eaa16171d
5 changed files with 82 additions and 25 deletions

View File

@ -1,6 +1,6 @@
# SecuBox UI & Theme History
_Last updated: 2026-02-11_
_Last updated: 2026-02-14_
1. **Unified Dashboard Refresh (2025-12-20)**
- Dashboard received the "sh-page-header" layout, hero stats, and SecuNav top tabs.
@ -1697,3 +1697,18 @@ git checkout HEAD -- index.html
- **API wrapper (wazuh/api.js)** with helper functions for alert levels and timestamps
- **Fixed jshn segfault issue** - simplified to printf-based JSON output
- Tested all RPCD methods via ubus calls
### 2026-02-14: mitmproxy WAF Wildcard Route Priority Fix
- **Fixed wildcard route matching in haproxy_router.py**
- Issue: `.gk2.secubox.in` wildcard (port 4000) matched before specific routes like `apr.gk2.secubox.in` (port 8928)
- Root cause: Python code expected `*.domain` format but HAProxy generates `.domain` format
- Fix: Support both `*.domain` and `.domain` wildcard formats
- Fix: Sort wildcards by length (longest/most specific first) to ensure proper priority
- **Added auto-reload capability**
- Routes file mtime checked every 10 requests
- Automatically reloads routes.json if file modified
- No container restart needed for route updates
- **Updated metablogizerctl integration**
- `_emancipate_mitmproxy()` now uses `mitmproxyctl sync-routes` instead of direct file manipulation
- Ensures HAProxy and mitmproxy routes stay in sync
- MetaBlogizer sites now properly routed through WAF

View File

@ -64,6 +64,15 @@ _Last updated: 2026-02-14 (WAF architecture configured)_
### Just Completed (2026-02-14)
- **mitmproxy WAF Wildcard Route Priority Fix** — DONE (2026-02-14)
- Fixed wildcard route matching in `haproxy_router.py`
- Issue: `.gk2.secubox.in` wildcard (port 4000) matched before specific routes like `apr.gk2.secubox.in` (port 8928)
- Fix: Support both `*.domain` and `.domain` wildcard formats
- Fix: Sort wildcards by length (longest/most specific first)
- Added auto-reload: Routes file checked every 10 requests, reloads if modified
- Updated `metablogizerctl` to use `mitmproxyctl sync-routes` instead of direct file manipulation
- MetaBlogizer sites now properly routed through WAF
- **Wazuh SIEM LuCI Dashboard** — DONE (2026-02-14)
- Created `luci-app-wazuh` package for unified Wazuh security monitoring
- 4 views: Overview, Alerts, File Integrity, Agents

View File

@ -1,4 +1,5 @@
'use strict';
'require baseclass';
'require rpc';
var callWazuh = rpc.declare({
@ -75,7 +76,7 @@ var callRestartAgent = rpc.declare({
expect: {}
});
return L.Class.extend({
return baseclass.extend({
getOverview: function() {
return callWazuh().then(function(res) {
return res.result || res;

View File

@ -843,19 +843,24 @@ _emancipate_haproxy() {
_emancipate_mitmproxy() {
local name="$1"
local domain="$2"
local port=$(uci_get site_${name}.port)
local routes_file="/srv/mitmproxy-in/haproxy-routes.json"
log_info "[WAF] Adding $domain to mitmproxy routes"
log_info "[WAF] Syncing mitmproxy routes from HAProxy config"
# Check if mitmproxy routes file exists
if [ ! -f "$routes_file" ]; then
log_warn "[WAF] mitmproxy routes file not found, skipping"
return 1
fi
# Use mitmproxyctl to sync routes from HAProxy UCI config
# This ensures all routes are in sync and mitmproxy will auto-reload
if command -v mitmproxyctl >/dev/null 2>&1; then
mitmproxyctl sync-routes 2>&1 | while read -r line; do
log_info "[WAF] $line"
done
log_info "[WAF] Routes synced - mitmproxy will auto-reload on next request"
else
log_warn "[WAF] mitmproxyctl not found, manually adding route"
# Fallback: directly add route to the routes file
local port=$(uci_get site_${name}.port)
local routes_file="/srv/mitmproxy-in/haproxy-routes.json"
# Add domain to mitmproxy routes using Python
python3 -c "
if [ -f "$routes_file" ]; then
python3 -c "
import json
try:
with open('$routes_file') as f:
@ -867,15 +872,7 @@ try:
except Exception as e:
print(f'[WAF] Error: {e}')
" 2>/dev/null
# Restart mitmproxy-in container to reload routes
if command -v lxc-stop >/dev/null 2>&1; then
log_info "[WAF] Restarting mitmproxy-in container..."
lxc-stop -n mitmproxy-in 2>/dev/null
sleep 1
lxc-start -n mitmproxy-in 2>/dev/null
sleep 2
log_info "[WAF] mitmproxy-in restarted"
fi
fi
}

View File

@ -19,6 +19,9 @@ DEFAULT_BACKEND = ("127.0.0.1", 8081) # LuCI fallback
class HaproxyRouter:
def __init__(self):
self.routes = {}
self._routes_mtime = 0
self._check_interval = 10 # Check file every N requests
self._request_count = 0
self._load_routes()
ctx.log.info(f"HAProxy Router loaded with {len(self.routes)} routes")
@ -26,6 +29,7 @@ class HaproxyRouter:
"""Load routing table from JSON file"""
if os.path.exists(ROUTES_FILE):
try:
self._routes_mtime = os.path.getmtime(ROUTES_FILE)
with open(ROUTES_FILE, 'r') as f:
self.routes = json.load(f)
ctx.log.info(f"Loaded routes: {list(self.routes.keys())}")
@ -36,6 +40,17 @@ class HaproxyRouter:
ctx.log.warn(f"Routes file not found: {ROUTES_FILE}")
self._generate_default_routes()
def _check_reload_routes(self):
"""Check if routes file has changed and reload if needed"""
try:
if os.path.exists(ROUTES_FILE):
mtime = os.path.getmtime(ROUTES_FILE)
if mtime > self._routes_mtime:
ctx.log.info("Routes file changed, reloading...")
self._load_routes()
except Exception as e:
ctx.log.error(f"Error checking routes file: {e}")
def _generate_default_routes(self):
"""Generate default routes from UCI if available"""
self.routes = {
@ -62,22 +77,42 @@ class HaproxyRouter:
# Remove port from host if present
hostname = host.split(':')[0].lower()
# 1. Try exact match first
if hostname in self.routes:
backend = self.routes[hostname]
return (backend[0], backend[1])
# Try wildcard matching
# 2. Try wildcard matching - collect all wildcard patterns
# Support both "*.domain" and ".domain" formats
wildcards = []
for pattern, backend in self.routes.items():
if pattern.startswith('*.'):
suffix = pattern[2:]
if hostname.endswith(suffix):
return (backend[0], backend[1])
# Standard wildcard: *.gk2.secubox.in
suffix = pattern[1:] # Keep the dot: .gk2.secubox.in
wildcards.append((suffix, backend))
elif pattern.startswith('.'):
# HAProxy-style wildcard: .gk2.secubox.in
suffix = pattern # Already has dot: .gk2.secubox.in
wildcards.append((suffix, backend))
# Sort by suffix length descending - longest (most specific) first
wildcards.sort(key=lambda x: len(x[0]), reverse=True)
for suffix, backend in wildcards:
if hostname.endswith(suffix):
return (backend[0], backend[1])
ctx.log.warn(f"No route for {hostname}, using default")
return DEFAULT_BACKEND
def request(self, flow: http.HTTPFlow):
"""Route request to appropriate backend"""
# Periodically check if routes file has changed
self._request_count += 1
if self._request_count >= self._check_interval:
self._request_count = 0
self._check_reload_routes()
host = flow.request.host_header or flow.request.host
backend = self._get_backend(host)