#!/bin/sh # RPCD backend for mitmproxy LuCI app . /usr/share/libubox/jshn.sh CONFIG="mitmproxy" LXC_NAME="mitmproxy" LXC_PATH="/srv/lxc" LXC_ROOTFS="$LXC_PATH/$LXC_NAME/rootfs" MITMPROXY_CACHE="/tmp/secubox/mitmproxy.json" # WAF input instance data path (for threat stats) WAF_DATA_PATH="/srv/mitmproxy-in" # Read cached status for fast API responses get_cached_status() { if [ -f "$MITMPROXY_CACHE" ]; then cat "$MITMPROXY_CACHE" else echo '{"running":0,"threats_today":0,"autobans":0,"pending":0,"wan_enabled":0,"lan_enabled":0,"timestamp":0}' fi } uci_get() { uci -q get ${CONFIG}.$1; } uci_set() { uci set ${CONFIG}.$1="$2"; } get_status() { local enabled=$(uci_get main.enabled) local web_port=$(uci_get main.web_port) local proxy_port=$(uci_get main.proxy_port) local data_path=$(uci_get main.data_path) local mode=$(uci_get main.mode) local haproxy_router_enabled=$(uci_get haproxy_router.enabled) local haproxy_listen_port=$(uci_get haproxy_router.listen_port) # WAN Protection settings local wan_protection_enabled=$(uci_get wan_protection.enabled) local wan_interface=$(uci_get wan_protection.wan_interface) local crowdsec_feed=$(uci_get wan_protection.crowdsec_feed) local block_bots=$(uci_get wan_protection.block_bots) # Check for LXC availability local lxc_available=0 command -v lxc-start >/dev/null 2>&1 && lxc_available=1 # Check if container is running (check both mitmproxy-in and mitmproxy-out) local running=0 if [ "$lxc_available" = "1" ]; then (lxc-info -n mitmproxy-in -s 2>/dev/null | grep -q "RUNNING" || \ lxc-info -n mitmproxy-out -s 2>/dev/null | grep -q "RUNNING" || \ lxc-info -n "$LXC_NAME" -s 2>/dev/null | grep -q "RUNNING") && running=1 fi # Check if installed (rootfs exists) local installed=0 [ -d "$LXC_ROOTFS" ] && [ -x "$LXC_ROOTFS/usr/bin/mitmproxy" ] && installed=1 # Check nftables status for transparent mode local nft_active=0 if [ "$mode" = "transparent" ] && command -v nft >/dev/null 2>&1; then nft list table inet mitmproxy >/dev/null 2>&1 && nft_active=1 fi # Check nftables status for WAN protection mode local nft_wan_active=0 if [ "$wan_protection_enabled" = "1" ] && command -v nft >/dev/null 2>&1; then nft list table inet mitmproxy_wan >/dev/null 2>&1 && nft_wan_active=1 fi # Get authentication token from container data path local token="" local token_file="${data_path:-/srv/mitmproxy}/.mitmproxy_token" [ -f "$token_file" ] && token=$(cat "$token_file" 2>/dev/null | tr -d '\n') # Auto-ban stats local autoban_enabled=$(uci_get autoban.enabled) local autoban_sensitivity=$(uci_get autoban.sensitivity) local autoban_duration=$(uci_get autoban.ban_duration) # Count threats today - use WAF input instance data local threats_today=0 local threats_log="${WAF_DATA_PATH}/threats.log" if [ -f "$threats_log" ]; then local today=$(date -u +%Y-%m-%d) threats_today=$(grep -c "\"timestamp\": \"$today" "$threats_log" 2>/dev/null) : ${threats_today:=0} fi # Count processed autobans - use WAF input instance data local autobans_total=0 local autobans_today=0 local autoban_log="${WAF_DATA_PATH}/autoban-processed.log" if [ -f "$autoban_log" ]; then autobans_total=$(wc -l < "$autoban_log" 2>/dev/null || echo 0) local today=$(date +%Y-%m-%d) autobans_today=$(grep -c "^$today" "$autoban_log" 2>/dev/null) : ${autobans_today:=0} fi # Pending autoban requests - use WAF input instance data local autobans_pending=0 local autoban_requests="${WAF_DATA_PATH}/autoban-requests.log" if [ -f "$autoban_requests" ] && [ -s "$autoban_requests" ]; then autobans_pending=$(wc -l < "$autoban_requests" 2>/dev/null || echo 0) fi cat </dev/null 2>&1 || { uci set mitmproxy.main=mitmproxy uci set mitmproxy.main.enabled='0' uci set mitmproxy.main.mode='regular' } uci -q get mitmproxy.wan_protection >/dev/null 2>&1 || { uci set mitmproxy.wan_protection=wan_protection uci set mitmproxy.wan_protection.enabled='0' } uci -q get mitmproxy.transparent >/dev/null 2>&1 || { uci set mitmproxy.transparent=transparent uci set mitmproxy.transparent.enabled='0' } uci -q get mitmproxy.dpi_mirror >/dev/null 2>&1 || { uci set mitmproxy.dpi_mirror=dpi_mirror uci set mitmproxy.dpi_mirror.enabled='0' } uci -q get mitmproxy.filtering >/dev/null 2>&1 || { uci set mitmproxy.filtering=filtering uci set mitmproxy.filtering.enabled='0' } # Apply main settings [ -n "$mode" ] && uci_set main.mode "$mode" [ -n "$enabled" ] && uci_set main.enabled "$enabled" [ -n "$proxy_port" ] && uci_set main.proxy_port "$proxy_port" [ -n "$web_port" ] && uci_set main.web_port "$web_port" [ -n "$web_host" ] && uci_set main.web_host "$web_host" [ -n "$data_path" ] && uci_set main.data_path "$data_path" [ -n "$memory_limit" ] && uci_set main.memory_limit "$memory_limit" [ -n "$upstream_proxy" ] && uci_set main.upstream_proxy "$upstream_proxy" [ -n "$reverse_target" ] && uci_set main.reverse_target "$reverse_target" [ -n "$ssl_insecure" ] && uci_set main.ssl_insecure "$ssl_insecure" [ -n "$anticache" ] && uci_set main.anticache "$anticache" [ -n "$anticomp" ] && uci_set main.anticomp "$anticomp" # Apply WAN protection settings [ -n "$wan_protection_enabled" ] && uci_set wan_protection.enabled "$wan_protection_enabled" [ -n "$wan_interface" ] && uci_set wan_protection.wan_interface "$wan_interface" [ -n "$wan_http_port" ] && uci_set wan_protection.wan_http_port "$wan_http_port" [ -n "$wan_https_port" ] && uci_set wan_protection.wan_https_port "$wan_https_port" [ -n "$crowdsec_feed" ] && uci_set wan_protection.crowdsec_feed "$crowdsec_feed" [ -n "$block_bots" ] && uci_set wan_protection.block_bots "$block_bots" [ -n "$rate_limit" ] && uci_set wan_protection.rate_limit "$rate_limit" # Apply transparent settings (LAN) [ -n "$transparent_enabled" ] && uci_set transparent.enabled "$transparent_enabled" [ -n "$transparent_interface" ] && uci_set transparent.interface "$transparent_interface" [ -n "$redirect_http" ] && uci_set transparent.redirect_http "$redirect_http" [ -n "$redirect_https" ] && uci_set transparent.redirect_https "$redirect_https" # Apply DPI mirror settings [ -n "$dpi_mirror_enabled" ] && uci_set dpi_mirror.enabled "$dpi_mirror_enabled" [ -n "$dpi_interface" ] && uci_set dpi_mirror.dpi_interface "$dpi_interface" [ -n "$mirror_wan" ] && uci_set dpi_mirror.mirror_wan "$mirror_wan" [ -n "$mirror_lan" ] && uci_set dpi_mirror.mirror_lan "$mirror_lan" # Apply filtering settings [ -n "$filtering_enabled" ] && uci_set filtering.enabled "$filtering_enabled" [ -n "$log_requests" ] && uci_set filtering.log_requests "$log_requests" [ -n "$filter_cdn" ] && uci_set filtering.filter_cdn "$filter_cdn" [ -n "$filter_media" ] && uci_set filtering.filter_media "$filter_media" [ -n "$block_ads" ] && uci_set filtering.block_ads "$block_ads" uci commit mitmproxy # Restart service to apply firewall rules if enabled and apply_now is set local is_enabled=$(uci_get main.enabled) local restarted=0 if [ "$is_enabled" = "1" ] && [ "$apply_now" = "1" ]; then /etc/init.d/mitmproxy restart >/dev/null 2>&1 & restarted=1 fi json_add_boolean "success" 1 if [ "$restarted" = "1" ]; then json_add_string "message" "Settings saved and applied" json_add_boolean "restarted" 1 else json_add_string "message" "Settings saved" json_add_boolean "restarted" 0 fi json_dump } set_mode() { read input json_load "$input" local mode apply_now json_get_var mode mode json_get_var apply_now apply_now json_init if [ -z "$mode" ]; then json_add_boolean "success" 0 json_add_string "error" "Mode is required" json_dump return fi # Validate mode case "$mode" in regular|transparent|upstream|reverse) ;; *) json_add_boolean "success" 0 json_add_string "error" "Invalid mode: $mode" json_dump return ;; esac # Ensure section exists uci -q get mitmproxy.main >/dev/null 2>&1 || { uci set mitmproxy.main=mitmproxy uci set mitmproxy.main.enabled='0' } uci_set main.mode "$mode" uci commit mitmproxy # Restart to apply firewall rules if needed local is_enabled=$(uci_get main.enabled) local restarted=0 if [ "$is_enabled" = "1" ] && [ "$apply_now" = "1" ]; then /etc/init.d/mitmproxy restart >/dev/null 2>&1 & restarted=1 fi json_add_boolean "success" 1 json_add_string "mode" "$mode" if [ "$restarted" = "1" ]; then json_add_string "message" "Mode set to $mode and applied" json_add_boolean "restarted" 1 else json_add_string "message" "Mode set to $mode" json_add_boolean "restarted" 0 fi json_dump } setup_firewall() { json_init if ! command -v mitmproxyctl >/dev/null 2>&1; then json_add_boolean "success" 0 json_add_string "error" "mitmproxyctl not found" json_dump return fi mitmproxyctl firewall-setup >/tmp/mitmproxy-fw.log 2>&1 local result=$? if [ $result -eq 0 ]; then json_add_boolean "success" 1 json_add_string "message" "Firewall rules applied" else json_add_boolean "success" 0 json_add_string "error" "Failed to setup firewall rules" local log=$(cat /tmp/mitmproxy-fw.log 2>/dev/null) [ -n "$log" ] && json_add_string "details" "$log" fi json_dump } clear_firewall() { json_init if ! command -v mitmproxyctl >/dev/null 2>&1; then json_add_boolean "success" 0 json_add_string "error" "mitmproxyctl not found" json_dump return fi mitmproxyctl firewall-clear >/tmp/mitmproxy-fw.log 2>&1 local result=$? if [ $result -eq 0 ]; then json_add_boolean "success" 1 json_add_string "message" "Firewall rules cleared" else json_add_boolean "success" 0 json_add_string "error" "Failed to clear firewall rules" fi json_dump } do_install() { command -v mitmproxyctl >/dev/null 2>&1 && { mitmproxyctl install >/tmp/mitmproxy-install.log 2>&1 & echo '{"success":true,"message":"Installing"}'; } || echo '{"success":false,"error":"mitmproxyctl not found"}' } do_start() { [ -x /etc/init.d/mitmproxy ] && /etc/init.d/mitmproxy start >/dev/null 2>&1; echo '{"success":true}'; } do_stop() { [ -x /etc/init.d/mitmproxy ] && /etc/init.d/mitmproxy stop >/dev/null 2>&1; echo '{"success":true}'; } do_restart() { [ -x /etc/init.d/mitmproxy ] && /etc/init.d/mitmproxy restart >/dev/null 2>&1; echo '{"success":true}'; } get_alerts() { # Read alerts from host-visible JSONL log file # The analytics addon writes to /data/threats.log inside container # which is bind-mounted to /srv/mitmproxy-in/threats.log on host (WAF input) local log_file="${WAF_DATA_PATH}/threats.log" local max_alerts=50 local alerts_json="[]" # Read last N lines from the host-accessible log file if [ -f "$log_file" ]; then local lines=$(tail -n "$max_alerts" "$log_file" 2>/dev/null) if [ -n "$lines" ]; then # Convert JSONL to JSON array: join lines with commas, wrap in brackets alerts_json=$(echo "$lines" | awk ' BEGIN { printf "[" } NR > 1 { printf "," } { printf "%s", $0 } END { printf "]" } ') fi fi # Validate JSON - if invalid, return empty array if ! echo "$alerts_json" | jsonfilter -e '@' >/dev/null 2>&1; then alerts_json="[]" fi cat </dev/null 2>&1; then container_stats=$(lxc-attach -n mitmproxy-in -- cat /tmp/secubox-mitm-stats.json 2>/dev/null) fi # Fall back to host path if [ -z "$container_stats" ]; then [ -f "$stats_file" ] && container_stats=$(cat "$stats_file" 2>/dev/null) fi # Default stats [ -z "$container_stats" ] && container_stats='{"total":{"requests":0,"threats":0,"bots":0}}' cat </dev/null 2>&1; then subdomain_metrics=$(lxc-attach -n mitmproxy-in -- cat /tmp/secubox-subdomain-metrics.json 2>/dev/null) fi # Fall back to host path if [ -z "$subdomain_metrics" ]; then [ -f "$metrics_file" ] && subdomain_metrics=$(cat "$metrics_file" 2>/dev/null) fi # Default empty metrics if [ -z "$subdomain_metrics" ]; then subdomain_metrics='{"updated":null,"subdomains":{}}' fi cat < "$log_file" 2>/dev/null # Also clear the legacy alerts file echo "[]" > /tmp/secubox-mitm-alerts.json 2>/dev/null echo '{"success":true,"message":"Alerts cleared"}' } haproxy_enable() { json_init if ! command -v mitmproxyctl >/dev/null 2>&1; then json_add_boolean "success" 0 json_add_string "error" "mitmproxyctl not found" json_dump return fi mitmproxyctl haproxy-enable >/tmp/haproxy-enable.log 2>&1 local result=$? if [ $result -eq 0 ]; then json_add_boolean "success" 1 json_add_string "message" "HAProxy backend inspection enabled" else json_add_boolean "success" 0 json_add_string "error" "Failed to enable HAProxy inspection" local log=$(cat /tmp/haproxy-enable.log 2>/dev/null) [ -n "$log" ] && json_add_string "details" "$log" fi json_dump } haproxy_disable() { json_init if ! command -v mitmproxyctl >/dev/null 2>&1; then json_add_boolean "success" 0 json_add_string "error" "mitmproxyctl not found" json_dump return fi mitmproxyctl haproxy-disable >/tmp/haproxy-disable.log 2>&1 local result=$? if [ $result -eq 0 ]; then json_add_boolean "success" 1 json_add_string "message" "HAProxy backend inspection disabled" else json_add_boolean "success" 0 json_add_string "error" "Failed to disable HAProxy inspection" fi json_dump } sync_routes() { json_init if ! command -v mitmproxyctl >/dev/null 2>&1; then json_add_boolean "success" 0 json_add_string "error" "mitmproxyctl not found" json_dump return fi mitmproxyctl sync-routes >/tmp/sync-routes.log 2>&1 local result=$? if [ $result -eq 0 ]; then json_add_boolean "success" 1 json_add_string "message" "Routes synced from HAProxy" else json_add_boolean "success" 0 json_add_string "error" "Failed to sync routes" fi json_dump } get_bans() { # Get CrowdSec decisions as JSON and output directly local bans_json="[]" local total=0 local autoban=0 local crowdsec_count=0 if command -v cscli >/dev/null 2>&1; then bans_json=$(cscli decisions list -o json 2>/dev/null) if [ -z "$bans_json" ] || [ "$bans_json" = "null" ]; then bans_json="[]" else # Count using grep on the raw JSON - patterns match with/without spaces total=$(echo "$bans_json" | grep -c '"id":' 2>/dev/null) || total=0 autoban=$(echo "$bans_json" | grep -c '"origin":.*"cscli"' 2>/dev/null) || autoban=0 crowdsec_count=$(echo "$bans_json" | grep -c '"origin":.*"crowdsec"' 2>/dev/null) || crowdsec_count=0 fi fi # Build response with embedded bans array printf '{"success":true,"total":%d,"mitmproxy_autoban":%d,"crowdsec":%d,"bans":%s}\n' \ "$total" "$autoban" "$crowdsec_count" "$bans_json" } unban_ip() { read -r input local ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null) json_init if [ -z "$ip" ]; then json_add_boolean "success" 0 json_add_string "error" "IP address required" json_dump return fi if ! command -v cscli >/dev/null 2>&1; then json_add_boolean "success" 0 json_add_string "error" "CrowdSec not installed" json_dump return fi cscli decisions delete --ip "$ip" >/dev/null 2>&1 local result=$? if [ $result -eq 0 ]; then json_add_boolean "success" 1 json_add_string "message" "Unbanned $ip" else json_add_boolean "success" 0 json_add_string "error" "Failed to unban $ip" fi json_dump } wan_setup() { json_init if ! command -v mitmproxyctl >/dev/null 2>&1; then json_add_boolean "success" 0 json_add_string "error" "mitmproxyctl not found" json_dump return fi mitmproxyctl wan-setup >/tmp/wan-setup.log 2>&1 local result=$? if [ $result -eq 0 ]; then json_add_boolean "success" 1 json_add_string "message" "WAN protection rules applied" else json_add_boolean "success" 0 json_add_string "error" "Failed to setup WAN protection rules" local log=$(cat /tmp/wan-setup.log 2>/dev/null) [ -n "$log" ] && json_add_string "details" "$log" fi json_dump } wan_clear() { json_init if ! command -v mitmproxyctl >/dev/null 2>&1; then json_add_boolean "success" 0 json_add_string "error" "mitmproxyctl not found" json_dump return fi mitmproxyctl wan-clear >/tmp/wan-clear.log 2>&1 local result=$? if [ $result -eq 0 ]; then json_add_boolean "success" 1 json_add_string "message" "WAN protection rules cleared" else json_add_boolean "success" 0 json_add_string "error" "Failed to clear WAN protection rules" fi json_dump } get_waf_rules() { local rules_file="/srv/mitmproxy/waf-rules.json" if [ -f "$rules_file" ]; then cat "$rules_file" else echo "{"error":"WAF rules file not found"}" fi } toggle_waf_category() { read -r input local category=$(jsonfilter -s "$input" -e '@.category' 2>/dev/null) local enabled=$(jsonfilter -s "$input" -e '@.enabled' 2>/dev/null) local rules_file="/srv/mitmproxy/waf-rules.json" if [ -z "$category" ]; then echo '{"error":"Missing category"}' return fi if [ -f "$rules_file" ] && command -v jq >/dev/null 2>&1; then cp "$rules_file" "${rules_file}.bak" jq ".categories.\"$category\".enabled = $enabled" "$rules_file" > "${rules_file}.tmp" && mv "${rules_file}.tmp" "$rules_file" echo '{"success":true}' else echo '{"error":"Cannot update rules"}' fi } list_methods() { cat <<'EOFM' {"status":{},"status_cached":{},"settings":{},"save_settings":{"mode":"str","enabled":"bool","proxy_port":"int","web_port":"int","apply_now":"bool","wan_protection_enabled":"bool","wan_interface":"str"},"set_mode":{"mode":"str","apply_now":"bool"},"setup_firewall":{},"clear_firewall":{},"wan_setup":{},"wan_clear":{},"install":{},"start":{},"stop":{},"restart":{},"alerts":{},"threat_stats":{},"subdomain_metrics":{},"clear_alerts":{},"haproxy_enable":{},"haproxy_disable":{},"sync_routes":{},"bans":{},"unban":{"ip":"str"},"get_waf_rules":{},"toggle_waf_category":{"category":"str","enabled":"bool"}} EOFM } case "$1" in list) list_methods ;; call) case "$2" in status) get_status ;; status_cached) get_cached_status ;; settings) get_settings ;; save_settings) save_settings ;; set_mode) set_mode ;; setup_firewall) setup_firewall ;; clear_firewall) clear_firewall ;; wan_setup) wan_setup ;; wan_clear) wan_clear ;; install) do_install ;; start) do_start ;; stop) do_stop ;; restart) do_restart ;; alerts) get_alerts ;; threat_stats) get_threat_stats ;; subdomain_metrics) get_subdomain_metrics ;; clear_alerts) clear_alerts ;; haproxy_enable) haproxy_enable ;; haproxy_disable) haproxy_disable ;; sync_routes) sync_routes ;; bans) get_bans ;; unban) unban_ip ;; get_waf_rules) get_waf_rules ;; toggle_waf_category) toggle_waf_category ;; *) echo '{"error":"Unknown method"}' ;; esac ;; *) echo '{"error":"Unknown command"}' ;; esac