From 9eaa16171d67ea23f6a83a874acc1da9c30fdfc5 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Sat, 14 Feb 2026 16:03:43 +0100 Subject: [PATCH] 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 --- .claude/HISTORY.md | 17 +++++++- .claude/WIP.md | 9 ++++ .../htdocs/luci-static/resources/wazuh/api.js | 3 +- .../files/usr/sbin/metablogizerctl | 35 +++++++-------- .../srv/mitmproxy/addons/haproxy_router.py | 43 +++++++++++++++++-- 5 files changed, 82 insertions(+), 25 deletions(-) diff --git a/.claude/HISTORY.md b/.claude/HISTORY.md index 7c49f2fe..4b800481 100644 --- a/.claude/HISTORY.md +++ b/.claude/HISTORY.md @@ -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 diff --git a/.claude/WIP.md b/.claude/WIP.md index ef1e279f..fa731065 100644 --- a/.claude/WIP.md +++ b/.claude/WIP.md @@ -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 diff --git a/package/secubox/luci-app-wazuh/htdocs/luci-static/resources/wazuh/api.js b/package/secubox/luci-app-wazuh/htdocs/luci-static/resources/wazuh/api.js index a7b7dda8..fee395dc 100644 --- a/package/secubox/luci-app-wazuh/htdocs/luci-static/resources/wazuh/api.js +++ b/package/secubox/luci-app-wazuh/htdocs/luci-static/resources/wazuh/api.js @@ -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; diff --git a/package/secubox/secubox-app-metablogizer/files/usr/sbin/metablogizerctl b/package/secubox/secubox-app-metablogizer/files/usr/sbin/metablogizerctl index bd00a82d..4f2a61dd 100644 --- a/package/secubox/secubox-app-metablogizer/files/usr/sbin/metablogizerctl +++ b/package/secubox/secubox-app-metablogizer/files/usr/sbin/metablogizerctl @@ -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 } diff --git a/package/secubox/secubox-app-mitmproxy/root/srv/mitmproxy/addons/haproxy_router.py b/package/secubox/secubox-app-mitmproxy/root/srv/mitmproxy/addons/haproxy_router.py index dfa4748d..13e8a359 100644 --- a/package/secubox/secubox-app-mitmproxy/root/srv/mitmproxy/addons/haproxy_router.py +++ b/package/secubox/secubox-app-mitmproxy/root/srv/mitmproxy/addons/haproxy_router.py @@ -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)