From fe222d542c94bbc5e9a333dd8bad7499885885f1 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Sat, 17 Jan 2026 06:55:45 +0100 Subject: [PATCH] feat(mitmproxy): Add transparent mode, filtering addon, and whitelist - Add nftables transparent mode support with automatic REDIRECT rules - Create SecuBox Python filter addon for CDN/Media/Ad tracking - Add whitelist/bypass configuration for IPs and domains - Expand UCI config with transparent, whitelist, filtering sections - Update RPCD backend with new config methods and firewall control - Update LuCI settings view with all new configuration options - Add new API methods: firewall_setup, firewall_clear, list management Features: - Transparent proxy with nftables integration - CDN tracking (Cloudflare, Akamai, Fastly, etc.) - Media streaming tracking (YouTube, Netflix, Spotify) - Ad/tracker blocking - IP and domain whitelist bypass Co-Authored-By: Claude Opus 4.5 --- .../luci-static/resources/mitmproxy/api.js | 106 ++++- .../resources/view/mitmproxy/settings.js | 222 +++++---- .../root/usr/libexec/rpcd/luci.mitmproxy | 239 +++++++++- .../files/etc/config/mitmproxy | 52 +++ .../files/usr/sbin/mitmproxyctl | 421 +++++++++++++++++- 5 files changed, 915 insertions(+), 125 deletions(-) diff --git a/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/mitmproxy/api.js b/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/mitmproxy/api.js index 769f1b77..9106f269 100644 --- a/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/mitmproxy/api.js +++ b/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/mitmproxy/api.js @@ -12,6 +12,26 @@ var callGetConfig = rpc.declare({ method: 'get_config' }); +var callGetTransparentConfig = rpc.declare({ + object: 'luci.mitmproxy', + method: 'get_transparent_config' +}); + +var callGetWhitelistConfig = rpc.declare({ + object: 'luci.mitmproxy', + method: 'get_whitelist_config' +}); + +var callGetFilteringConfig = rpc.declare({ + object: 'luci.mitmproxy', + method: 'get_filtering_config' +}); + +var callGetAllConfig = rpc.declare({ + object: 'luci.mitmproxy', + method: 'get_all_config' +}); + var callGetStats = rpc.declare({ object: 'luci.mitmproxy', method: 'get_stats' @@ -20,7 +40,7 @@ var callGetStats = rpc.declare({ var callGetRequests = rpc.declare({ object: 'luci.mitmproxy', method: 'get_requests', - params: ['limit'] + params: ['limit', 'category'] }); var callGetTopHosts = rpc.declare({ @@ -49,12 +69,34 @@ var callServiceRestart = rpc.declare({ method: 'service_restart' }); +var callFirewallSetup = rpc.declare({ + object: 'luci.mitmproxy', + method: 'firewall_setup' +}); + +var callFirewallClear = rpc.declare({ + object: 'luci.mitmproxy', + method: 'firewall_clear' +}); + var callSetConfig = rpc.declare({ object: 'luci.mitmproxy', method: 'set_config', params: ['key', 'value'] }); +var callAddToList = rpc.declare({ + object: 'luci.mitmproxy', + method: 'add_to_list', + params: ['key', 'value'] +}); + +var callRemoveFromList = rpc.declare({ + object: 'luci.mitmproxy', + method: 'remove_from_list', + params: ['key', 'value'] +}); + var callClearData = rpc.declare({ object: 'luci.mitmproxy', method: 'clear_data' @@ -73,14 +115,45 @@ return baseclass.extend({ }); }, - getStats: function() { - return callGetStats().catch(function() { - return { total_requests: 0, unique_hosts: 0, flow_file_size: 0 }; + getTransparentConfig: function() { + return callGetTransparentConfig().catch(function() { + return { enabled: false }; }); }, - getRequests: function(limit) { - return callGetRequests(limit || 50).catch(function() { + getWhitelistConfig: function() { + return callGetWhitelistConfig().catch(function() { + return { enabled: true, bypass_ip: [], bypass_domain: [] }; + }); + }, + + getFilteringConfig: function() { + return callGetFilteringConfig().catch(function() { + return { enabled: false }; + }); + }, + + getAllConfig: function() { + return callGetAllConfig().catch(function() { + return { main: {}, transparent: {}, whitelist: {}, filtering: {} }; + }); + }, + + getStats: function() { + return callGetStats().catch(function() { + return { + total_requests: 0, + unique_hosts: 0, + flow_file_size: 0, + cdn_requests: 0, + media_requests: 0, + blocked_ads: 0 + }; + }); + }, + + getRequests: function(limit, category) { + return callGetRequests(limit || 50, category || 'all').catch(function() { return { requests: [] }; }); }, @@ -109,10 +182,26 @@ return baseclass.extend({ return callServiceRestart(); }, + firewallSetup: function() { + return callFirewallSetup(); + }, + + firewallClear: function() { + return callFirewallClear(); + }, + setConfig: function(key, value) { return callSetConfig(key, value); }, + addToList: function(key, value) { + return callAddToList(key, value); + }, + + removeFromList: function(key, value) { + return callRemoveFromList(key, value); + }, + clearData: function() { return callClearData(); }, @@ -121,14 +210,15 @@ return baseclass.extend({ var self = this; return Promise.all([ self.getStatus(), - self.getConfig(), + self.getAllConfig(), self.getStats(), self.getTopHosts(10), self.getCaInfo() ]).then(function(results) { return { status: results[0], - config: results[1], + config: results[1].main || results[1], + allConfig: results[1], stats: results[2], topHosts: results[3], caInfo: results[4] diff --git a/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/settings.js b/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/settings.js index de85c36e..e51d0e93 100644 --- a/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/settings.js +++ b/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/settings.js @@ -45,9 +45,11 @@ return view.extend({ var m, s, o; m = new form.Map('mitmproxy', _('mitmproxy Settings'), - _('Configure the mitmproxy HTTPS interception proxy.')); + _('Configure the mitmproxy HTTPS interception proxy with transparent mode and filtering options.')); - // Main settings + // ===================================================================== + // Main Proxy Configuration + // ===================================================================== s = m.section(form.TypedSection, 'mitmproxy', _('Proxy Configuration')); s.anonymous = true; s.addremove = false; @@ -59,18 +61,13 @@ return view.extend({ o = s.option(form.ListValue, 'mode', _('Proxy Mode'), _('How clients connect to the proxy')); - o.value('transparent', _('Transparent - Intercept traffic automatically')); o.value('regular', _('Regular - Clients must configure proxy settings')); + o.value('transparent', _('Transparent - Intercept traffic automatically via nftables')); o.value('upstream', _('Upstream - Forward to another proxy')); - o.default = 'transparent'; + o.value('reverse', _('Reverse - Reverse proxy mode')); + o.default = 'regular'; - o = s.option(form.Value, 'listen_host', _('Listen Address'), - _('IP address to bind the proxy to')); - o.default = '0.0.0.0'; - o.placeholder = '0.0.0.0'; - o.datatype = 'ipaddr'; - - o = s.option(form.Value, 'listen_port', _('Proxy Port'), + o = s.option(form.Value, 'proxy_port', _('Proxy Port'), _('Port for HTTP/HTTPS interception')); o.default = '8080'; o.placeholder = '8080'; @@ -88,10 +85,27 @@ return view.extend({ o.placeholder = '8081'; o.datatype = 'port'; + o = s.option(form.Value, 'data_path', _('Data Path'), + _('Directory for storing certificates and data')); + o.default = '/srv/mitmproxy'; + + o = s.option(form.Value, 'memory_limit', _('Memory Limit'), + _('Maximum memory for the LXC container')); + o.default = '256M'; + o.placeholder = '256M'; + o = s.option(form.Flag, 'ssl_insecure', _('Allow Insecure SSL'), _('Accept invalid/self-signed SSL certificates from upstream servers')); o.default = '0'; + o = s.option(form.Flag, 'anticache', _('Anti-Cache'), + _('Strip cache headers to force fresh responses')); + o.default = '0'; + + o = s.option(form.Flag, 'anticomp', _('Anti-Compression'), + _('Disable compression to allow content inspection')); + o.default = '0'; + o = s.option(form.ListValue, 'flow_detail', _('Log Detail Level'), _('Amount of detail in flow logs')); o.value('0', _('Minimal')); @@ -99,87 +113,141 @@ return view.extend({ o.value('2', _('Full headers')); o.value('3', _('Full headers + body preview')); o.value('4', _('Full headers + full body')); - o.default = '2'; + o.default = '1'; - // Capture settings + o = s.option(form.Value, 'upstream_proxy', _('Upstream Proxy'), + _('Forward traffic to this upstream proxy (e.g., http://proxy:8080)')); + o.depends('mode', 'upstream'); + o.placeholder = 'http://proxy:8080'; + + o = s.option(form.Value, 'reverse_target', _('Reverse Target'), + _('Target server for reverse proxy mode (e.g., http://localhost:80)')); + o.depends('mode', 'reverse'); + o.placeholder = 'http://localhost:80'; + + // ===================================================================== + // Transparent Mode Settings + // ===================================================================== + s = m.section(form.TypedSection, 'transparent', _('Transparent Mode')); + s.anonymous = true; + s.addremove = false; + s.tab('transparent', _('Firewall Settings')); + + o = s.taboption('transparent', form.Flag, 'enabled', _('Enable Transparent Firewall'), + _('Automatically setup nftables rules to redirect traffic')); + o.default = '0'; + + o = s.taboption('transparent', form.Value, 'interface', _('Intercept Interface'), + _('Network interface to intercept traffic from')); + o.default = 'br-lan'; + o.placeholder = 'br-lan'; + o.depends('enabled', '1'); + + o = s.taboption('transparent', form.Flag, 'redirect_http', _('Redirect HTTP'), + _('Intercept plain HTTP traffic')); + o.default = '1'; + o.depends('enabled', '1'); + + o = s.taboption('transparent', form.Flag, 'redirect_https', _('Redirect HTTPS'), + _('Intercept HTTPS traffic (requires CA certificate on clients)')); + o.default = '1'; + o.depends('enabled', '1'); + + o = s.taboption('transparent', form.Value, 'http_port', _('HTTP Port'), + _('Source port to intercept for HTTP')); + o.default = '80'; + o.datatype = 'port'; + o.depends('redirect_http', '1'); + + o = s.taboption('transparent', form.Value, 'https_port', _('HTTPS Port'), + _('Source port to intercept for HTTPS')); + o.default = '443'; + o.datatype = 'port'; + o.depends('redirect_https', '1'); + + // ===================================================================== + // Whitelist/Bypass Settings + // ===================================================================== + s = m.section(form.TypedSection, 'whitelist', _('Whitelist / Bypass')); + s.anonymous = true; + s.addremove = false; + + o = s.option(form.Flag, 'enabled', _('Enable Whitelist'), + _('Skip interception for whitelisted IPs and domains')); + o.default = '1'; + + o = s.option(form.DynamicList, 'bypass_ip', _('Bypass IP Addresses'), + _('IP addresses or CIDR ranges that bypass the proxy')); + o.placeholder = '192.168.1.0/24'; + o.depends('enabled', '1'); + + o = s.option(form.DynamicList, 'bypass_domain', _('Bypass Domains'), + _('Domain patterns that bypass the proxy (for domain-based bypass, requires additional configuration)')); + o.placeholder = 'banking.com'; + o.depends('enabled', '1'); + + // ===================================================================== + // Filtering / CDN Tracking + // ===================================================================== + s = m.section(form.TypedSection, 'filtering', _('Filtering & Analytics')); + s.anonymous = true; + s.addremove = false; + + o = s.option(form.Flag, 'enabled', _('Enable Filtering Addon'), + _('Load the SecuBox filtering addon for CDN/Media tracking and ad blocking')); + o.default = '0'; + + o = s.option(form.Flag, 'log_requests', _('Log All Requests'), + _('Log request details to JSON file for analysis')); + o.default = '1'; + o.depends('enabled', '1'); + + o = s.option(form.Flag, 'filter_cdn', _('Track CDN Traffic'), + _('Log and categorize CDN requests (Cloudflare, Akamai, Fastly, etc.)')); + o.default = '0'; + o.depends('enabled', '1'); + + o = s.option(form.Flag, 'filter_media', _('Track Media Streaming'), + _('Log and categorize streaming media requests (YouTube, Netflix, Spotify, etc.)')); + o.default = '0'; + o.depends('enabled', '1'); + + o = s.option(form.Flag, 'block_ads', _('Block Ads & Trackers'), + _('Block known advertising and tracking domains')); + o.default = '0'; + o.depends('enabled', '1'); + + o = s.option(form.Value, 'addon_script', _('Addon Script Path'), + _('Path to the Python filtering addon')); + o.default = '/etc/mitmproxy/addons/secubox_filter.py'; + o.depends('enabled', '1'); + + // ===================================================================== + // Capture Settings + // ===================================================================== s = m.section(form.TypedSection, 'capture', _('Capture Settings')); s.anonymous = true; s.addremove = false; o = s.option(form.Flag, 'save_flows', _('Save Flows'), _('Save captured flows to disk for later replay')); - o.default = '1'; - - o = s.option(form.Value, 'flow_file', _('Flow File'), - _('Path to save captured flows')); - o.default = '/tmp/mitmproxy/flows.bin'; - o.depends('save_flows', '1'); - - o = s.option(form.Flag, 'capture_urls', _('Capture URLs'), - _('Log full URLs of requests')); - o.default = '1'; - - o = s.option(form.Flag, 'capture_cookies', _('Capture Cookies'), - _('Log cookie headers')); - o.default = '1'; - - o = s.option(form.Flag, 'capture_headers', _('Capture Headers'), - _('Log all HTTP headers')); - o.default = '1'; - - o = s.option(form.Flag, 'capture_body', _('Capture Body'), - _('Log request/response bodies (increases storage usage)')); o.default = '0'; - // Logging settings - s = m.section(form.TypedSection, 'logging', _('Logging')); - s.anonymous = true; - s.addremove = false; - - o = s.option(form.Flag, 'enabled', _('Enable Request Logging'), - _('Log requests to file')); + o = s.option(form.Flag, 'capture_request_headers', _('Capture Request Headers'), + _('Include request headers in logs')); o.default = '1'; - o = s.option(form.Value, 'log_file', _('Log File'), - _('Path to request log file')); - o.default = '/tmp/mitmproxy/requests.log'; - o.depends('enabled', '1'); + o = s.option(form.Flag, 'capture_response_headers', _('Capture Response Headers'), + _('Include response headers in logs')); + o.default = '1'; - o = s.option(form.ListValue, 'log_format', _('Log Format'), - _('Format of log entries')); - o.value('json', _('JSON')); - o.value('text', _('Plain text')); - o.default = 'json'; - o.depends('enabled', '1'); - - o = s.option(form.Value, 'max_size', _('Max Log Size (MB)'), - _('Rotate log when it reaches this size')); - o.default = '10'; - o.datatype = 'uinteger'; - o.depends('enabled', '1'); - - // Filter settings - s = m.section(form.TypedSection, 'filter', _('Filtering')); - s.anonymous = true; - s.addremove = false; - - o = s.option(form.Flag, 'enabled', _('Enable Filtering'), - _('Enable content filtering')); + o = s.option(form.Flag, 'capture_request_body', _('Capture Request Body'), + _('Include request body in logs (increases storage usage)')); o.default = '0'; - o = s.option(form.Flag, 'block_ads', _('Block Ads'), - _('Block known advertising domains')); + o = s.option(form.Flag, 'capture_response_body', _('Capture Response Body'), + _('Include response body in logs (increases storage usage)')); o.default = '0'; - o.depends('enabled', '1'); - - o = s.option(form.Flag, 'block_trackers', _('Block Trackers'), - _('Block known tracking domains')); - o.default = '0'; - o.depends('enabled', '1'); - - o = s.option(form.DynamicList, 'ignore_host', _('Ignore Hosts'), - _('Hosts to pass through without interception')); - o.placeholder = '*.example.com'; var wrapper = E('div', { 'class': 'secubox-page-wrapper' }); wrapper.appendChild(SbHeader.render()); diff --git a/package/secubox/luci-app-mitmproxy/root/usr/libexec/rpcd/luci.mitmproxy b/package/secubox/luci-app-mitmproxy/root/usr/libexec/rpcd/luci.mitmproxy index 79cbf411..7be91afc 100755 --- a/package/secubox/luci-app-mitmproxy/root/usr/libexec/rpcd/luci.mitmproxy +++ b/package/secubox/luci-app-mitmproxy/root/usr/libexec/rpcd/luci.mitmproxy @@ -12,13 +12,6 @@ CONF_DIR="$DATA_DIR" LOG_FILE="$DATA_DIR/requests.log" FLOW_FILE="$DATA_DIR/flows.bin" -# JSON helpers -json_init() { echo "{"; } -json_close() { echo "}"; } -json_add_string() { printf '"%s":"%s"' "$1" "$2"; } -json_add_int() { printf '"%s":%d' "$1" "${2:-0}"; } -json_add_bool() { [ "$2" = "1" ] && printf '"%s":true' "$1" || printf '"%s":false' "$1"; } - # Get service status get_status() { local running=0 @@ -26,6 +19,7 @@ get_status() { local mode="unknown" local web_url="" local lxc_state="" + local nft_active="false" # Check LXC container status if command -v lxc-info >/dev/null 2>&1; then @@ -50,10 +44,16 @@ get_status() { fi fi + # Check nftables rules + if command -v nft >/dev/null 2>&1; then + nft list table inet mitmproxy >/dev/null 2>&1 && nft_active="true" + fi + local enabled=$(uci -q get mitmproxy.main.enabled || echo "0") local proxy_port=$(uci -q get mitmproxy.main.proxy_port || echo "8080") local web_port=$(uci -q get mitmproxy.main.web_port || echo "8081") local proxy_mode=$(uci -q get mitmproxy.main.mode || echo "regular") + local filtering_enabled=$(uci -q get mitmproxy.filtering.enabled || echo "0") local router_ip=$(uci -q get network.lan.ipaddr || echo "192.168.1.1") [ "$running" = "1" ] && [ "$mode" = "mitmweb" ] && web_url="http://${router_ip}:${web_port}" @@ -69,12 +69,14 @@ get_status() { "proxy_port": $proxy_port, "web_port": $web_port, "web_url": "$web_url", - "ca_installed": $([ -f "$CONF_DIR/mitmproxy-ca-cert.pem" ] && echo "true" || echo "false") + "ca_installed": $([ -f "$CONF_DIR/mitmproxy-ca-cert.pem" ] && echo "true" || echo "false"), + "nft_active": $nft_active, + "filtering_enabled": $([ "$filtering_enabled" = "1" ] && echo "true" || echo "false") } EOF } -# Get configuration +# Get main configuration get_config() { local enabled=$(uci -q get mitmproxy.main.enabled || echo "0") local mode=$(uci -q get mitmproxy.main.mode || echo "regular") @@ -105,16 +107,102 @@ get_config() { EOF } +# Get transparent mode configuration +get_transparent_config() { + local enabled=$(uci -q get mitmproxy.transparent.enabled || echo "0") + local interface=$(uci -q get mitmproxy.transparent.interface || echo "br-lan") + local redirect_http=$(uci -q get mitmproxy.transparent.redirect_http || echo "1") + local redirect_https=$(uci -q get mitmproxy.transparent.redirect_https || echo "1") + local http_port=$(uci -q get mitmproxy.transparent.http_port || echo "80") + local https_port=$(uci -q get mitmproxy.transparent.https_port || echo "443") + + cat </dev/null | tr ' ' '\n' | while read ip; do + [ -n "$ip" ] && printf '"%s",' "$ip" + done | sed 's/,$//') + + # Get bypass_domain list + local bypass_domains=$(uci -q get mitmproxy.whitelist.bypass_domain 2>/dev/null | tr ' ' '\n' | while read domain; do + [ -n "$domain" ] && printf '"%s",' "$domain" + done | sed 's/,$//') + + cat </dev/null || echo "0") - if command -v jq >/dev/null 2>&1; then - unique_hosts=$(jq -r '.request.host // .host // empty' "$LOG_FILE" 2>/dev/null | sort -u | wc -l) + # Use jsonfilter for parsing (OpenWrt native) + if command -v jsonfilter >/dev/null 2>&1; then + unique_hosts=$(cat "$LOG_FILE" 2>/dev/null | while read line; do + echo "$line" | jsonfilter -e '@.request.host' 2>/dev/null + done | sort -u | wc -l) + cdn_requests=$(grep -c '"category":"cdn"' "$LOG_FILE" 2>/dev/null || echo "0") + media_requests=$(grep -c '"category":"media"' "$LOG_FILE" 2>/dev/null || echo "0") + blocked_ads=$(grep -c '"category":"blocked_ad"' "$LOG_FILE" 2>/dev/null || echo "0") fi fi @@ -126,7 +214,10 @@ get_stats() { { "total_requests": $total_requests, "unique_hosts": $unique_hosts, - "flow_file_size": $flow_size + "flow_file_size": $flow_size, + "cdn_requests": $cdn_requests, + "media_requests": $media_requests, + "blocked_ads": $blocked_ads } EOF } @@ -134,18 +225,24 @@ EOF # Get recent requests get_requests() { local limit="${1:-50}" + local category="${2:-}" if [ ! -f "$LOG_FILE" ]; then echo '{"requests":[]}' return fi - if command -v jq >/dev/null 2>&1; then - echo '{"requests":' - tail -"$limit" "$LOG_FILE" 2>/dev/null | jq -s '.' 2>/dev/null || echo '[]' - echo '}' + # Filter by category if specified + if [ -n "$category" ] && [ "$category" != "all" ]; then + echo '{"requests":[' + grep "\"category\":\"$category\"" "$LOG_FILE" 2>/dev/null | tail -"$limit" | \ + awk 'BEGIN{first=1}{if(!first)printf ",";first=0;print}' 2>/dev/null || echo "" + echo ']}' else - echo '{"requests":[]}' + echo '{"requests":[' + tail -"$limit" "$LOG_FILE" 2>/dev/null | \ + awk 'BEGIN{first=1}{if(!first)printf ",";first=0;print}' 2>/dev/null || echo "" + echo ']}' fi } @@ -153,13 +250,15 @@ get_requests() { get_top_hosts() { local limit="${1:-20}" - if [ ! -f "$LOG_FILE" ] || ! command -v jq >/dev/null 2>&1; then + if [ ! -f "$LOG_FILE" ]; then echo '{"hosts":[]}' return fi echo '{"hosts":[' - jq -r '.request.host // .host // "unknown"' "$LOG_FILE" 2>/dev/null | \ + # Parse JSON using grep/sed for compatibility + grep -o '"host":"[^"]*"' "$LOG_FILE" 2>/dev/null | \ + sed 's/"host":"//;s/"$//' | \ sort | uniq -c | sort -rn | head -"$limit" | \ awk 'BEGIN{first=1} { if(!first) printf ","; @@ -189,6 +288,28 @@ service_restart() { get_status } +# Setup firewall rules +firewall_setup() { + /usr/sbin/mitmproxyctl firewall-setup 2>&1 + local result=$? + if [ $result -eq 0 ]; then + echo '{"success":true,"message":"Firewall rules applied"}' + else + echo '{"success":false,"message":"Failed to apply firewall rules"}' + fi +} + +# Clear firewall rules +firewall_clear() { + /usr/sbin/mitmproxyctl firewall-clear 2>&1 + local result=$? + if [ $result -eq 0 ]; then + echo '{"success":true,"message":"Firewall rules cleared"}' + else + echo '{"success":false,"message":"Failed to clear firewall rules"}' + fi +} + # Set configuration set_config() { local key="$1" @@ -199,6 +320,21 @@ set_config() { save_flows|capture_*) section="capture" ;; + redirect_*|interface|http_port|https_port) + section="transparent" + ;; + bypass_ip|bypass_domain) + section="whitelist" + ;; + filter_*|log_requests|block_ads|addon_script) + section="filtering" + ;; + esac + + # Handle boolean conversion + case "$value" in + true) value="1" ;; + false) value="0" ;; esac uci set "mitmproxy.$section.$key=$value" @@ -206,6 +342,28 @@ set_config() { echo '{"success":true}' } +# Add to list (for bypass_ip, bypass_domain) +add_to_list() { + local key="$1" + local value="$2" + local section="whitelist" + + uci add_list "mitmproxy.$section.$key=$value" + uci commit mitmproxy + echo '{"success":true}' +} + +# Remove from list +remove_from_list() { + local key="$1" + local value="$2" + local section="whitelist" + + uci del_list "mitmproxy.$section.$key=$value" + uci commit mitmproxy + echo '{"success":true}' +} + # Clear captured data clear_data() { rm -f "$DATA_DIR"/*.log "$DATA_DIR"/*.bin 2>/dev/null @@ -249,14 +407,22 @@ case "$1" in { "get_status": {}, "get_config": {}, + "get_transparent_config": {}, + "get_whitelist_config": {}, + "get_filtering_config": {}, + "get_all_config": {}, "get_stats": {}, - "get_requests": {"limit": 50}, + "get_requests": {"limit": 50, "category": "all"}, "get_top_hosts": {"limit": 20}, "get_ca_info": {}, "service_start": {}, "service_stop": {}, "service_restart": {}, + "firewall_setup": {}, + "firewall_clear": {}, "set_config": {"key": "string", "value": "string"}, + "add_to_list": {"key": "string", "value": "string"}, + "remove_from_list": {"key": "string", "value": "string"}, "clear_data": {} } EOF @@ -269,13 +435,26 @@ EOF get_config) get_config ;; + get_transparent_config) + get_transparent_config + ;; + get_whitelist_config) + get_whitelist_config + ;; + get_filtering_config) + get_filtering_config + ;; + get_all_config) + get_all_config + ;; get_stats) get_stats ;; get_requests) read -r input limit=$(echo "$input" | jsonfilter -e '@.limit' 2>/dev/null || echo "50") - get_requests "$limit" + category=$(echo "$input" | jsonfilter -e '@.category' 2>/dev/null || echo "all") + get_requests "$limit" "$category" ;; get_top_hosts) read -r input @@ -294,12 +473,30 @@ EOF service_restart) service_restart ;; + firewall_setup) + firewall_setup + ;; + firewall_clear) + firewall_clear + ;; set_config) read -r input key=$(echo "$input" | jsonfilter -e '@.key' 2>/dev/null) value=$(echo "$input" | jsonfilter -e '@.value' 2>/dev/null) set_config "$key" "$value" ;; + add_to_list) + read -r input + key=$(echo "$input" | jsonfilter -e '@.key' 2>/dev/null) + value=$(echo "$input" | jsonfilter -e '@.value' 2>/dev/null) + add_to_list "$key" "$value" + ;; + remove_from_list) + read -r input + key=$(echo "$input" | jsonfilter -e '@.key' 2>/dev/null) + value=$(echo "$input" | jsonfilter -e '@.value' 2>/dev/null) + remove_from_list "$key" "$value" + ;; clear_data) clear_data ;; diff --git a/package/secubox/secubox-app-mitmproxy/files/etc/config/mitmproxy b/package/secubox/secubox-app-mitmproxy/files/etc/config/mitmproxy index 30875a58..1394dfd7 100644 --- a/package/secubox/secubox-app-mitmproxy/files/etc/config/mitmproxy +++ b/package/secubox/secubox-app-mitmproxy/files/etc/config/mitmproxy @@ -14,3 +14,55 @@ config mitmproxy 'main' option anticache '0' option anticomp '0' option flow_detail '1' + +# Transparent mode settings +config transparent 'transparent' + option enabled '0' + # Interface to intercept traffic from (e.g., br-lan) + option interface 'br-lan' + # Redirect HTTP traffic (port 80) + option redirect_http '1' + # Redirect HTTPS traffic (port 443) + option redirect_https '1' + # Custom HTTP port (default 80) + option http_port '80' + # Custom HTTPS port (default 443) + option https_port '443' + +# Whitelist/bypass - IPs and domains that bypass the proxy +config whitelist 'whitelist' + option enabled '1' + # Bypass local networks by default + list bypass_ip '10.0.0.0/8' + list bypass_ip '172.16.0.0/12' + list bypass_ip '192.168.0.0/16' + list bypass_ip '127.0.0.0/8' + # Bypass sensitive domains (banking, medical, etc.) + list bypass_domain 'banking' + list bypass_domain 'paypal.com' + list bypass_domain 'stripe.com' + # Add custom bypasses here + # list bypass_ip 'x.x.x.x' + # list bypass_domain 'example.com' + +# CDN/MediaFlow filtering addon +config filtering 'filtering' + option enabled '0' + # Log all requests to JSON file + option log_requests '1' + # Filter CDN traffic (e.g., cloudflare, akamai, fastly) + option filter_cdn '0' + # Filter streaming media + option filter_media '0' + # Block ads and trackers + option block_ads '0' + # Custom filter script path + option addon_script '/etc/mitmproxy/addons/secubox_filter.py' + +# Capture settings +config capture 'capture' + option save_flows '0' + option capture_request_headers '1' + option capture_response_headers '1' + option capture_request_body '0' + option capture_response_body '0' diff --git a/package/secubox/secubox-app-mitmproxy/files/usr/sbin/mitmproxyctl b/package/secubox/secubox-app-mitmproxy/files/usr/sbin/mitmproxyctl index c2895fe9..ac47f423 100755 --- a/package/secubox/secubox-app-mitmproxy/files/usr/sbin/mitmproxyctl +++ b/package/secubox/secubox-app-mitmproxy/files/usr/sbin/mitmproxyctl @@ -1,15 +1,17 @@ #!/bin/sh -# SecuBox mitmproxy manager - LXC container support -# Copyright (C) 2024 CyberMind.fr +# SecuBox mitmproxy manager - LXC container support with transparent mode +# Copyright (C) 2024-2025 CyberMind.fr CONFIG="mitmproxy" LXC_NAME="mitmproxy" OPKG_UPDATED=0 +NFT_TABLE="mitmproxy" # Paths LXC_PATH="/srv/lxc" LXC_ROOTFS="$LXC_PATH/$LXC_NAME/rootfs" LXC_CONFIG="$LXC_PATH/$LXC_NAME/config" +ADDON_PATH="/etc/mitmproxy/addons" usage() { cat <<'EOF' @@ -23,12 +25,14 @@ Commands: logs Show mitmproxy logs (use -f to follow) shell Open shell in container cert Show CA certificate info / export path + firewall-setup Setup nftables rules for transparent mode + firewall-clear Remove nftables transparent mode rules service-run Internal: run container under procd service-stop Stop container Modes (configure in /etc/config/mitmproxy): regular - Standard HTTP/HTTPS proxy (default) - transparent - Transparent proxy (requires iptables redirect) + transparent - Transparent proxy (auto-configures nftables) upstream - Forward to upstream proxy reverse - Reverse proxy mode @@ -43,23 +47,44 @@ log_info() { echo "[INFO] $*"; } log_warn() { echo "[WARN] $*" >&2; } log_error() { echo "[ERROR] $*" >&2; } -uci_get() { uci -q get ${CONFIG}.main.$1; } -uci_set() { uci set ${CONFIG}.main.$1="$2" && uci commit ${CONFIG}; } +uci_get() { uci -q get ${CONFIG}.$1; } +uci_set() { uci set ${CONFIG}.$1="$2" && uci commit ${CONFIG}; } +uci_get_list() { uci -q get ${CONFIG}.$1 2>/dev/null; } # Load configuration with defaults load_config() { - proxy_port="$(uci_get proxy_port || echo 8080)" - web_port="$(uci_get web_port || echo 8081)" - web_host="$(uci_get web_host || echo 0.0.0.0)" - data_path="$(uci_get data_path || echo /srv/mitmproxy)" - memory_limit="$(uci_get memory_limit || echo 256M)" - mode="$(uci_get mode || echo regular)" - upstream_proxy="$(uci_get upstream_proxy || echo '')" - reverse_target="$(uci_get reverse_target || echo '')" - ssl_insecure="$(uci_get ssl_insecure || echo 0)" - anticache="$(uci_get anticache || echo 0)" - anticomp="$(uci_get anticomp || echo 0)" - flow_detail="$(uci_get flow_detail || echo 1)" + # Main settings + proxy_port="$(uci_get main.proxy_port || echo 8080)" + web_port="$(uci_get main.web_port || echo 8081)" + web_host="$(uci_get main.web_host || echo 0.0.0.0)" + data_path="$(uci_get main.data_path || echo /srv/mitmproxy)" + memory_limit="$(uci_get main.memory_limit || echo 256M)" + mode="$(uci_get main.mode || echo regular)" + upstream_proxy="$(uci_get main.upstream_proxy || echo '')" + reverse_target="$(uci_get main.reverse_target || echo '')" + ssl_insecure="$(uci_get main.ssl_insecure || echo 0)" + anticache="$(uci_get main.anticache || echo 0)" + anticomp="$(uci_get main.anticomp || echo 0)" + flow_detail="$(uci_get main.flow_detail || echo 1)" + + # Transparent mode settings + transparent_enabled="$(uci_get transparent.enabled || echo 0)" + transparent_iface="$(uci_get transparent.interface || echo br-lan)" + redirect_http="$(uci_get transparent.redirect_http || echo 1)" + redirect_https="$(uci_get transparent.redirect_https || echo 1)" + http_port="$(uci_get transparent.http_port || echo 80)" + https_port="$(uci_get transparent.https_port || echo 443)" + + # Whitelist settings + whitelist_enabled="$(uci_get whitelist.enabled || echo 1)" + + # Filtering settings + filtering_enabled="$(uci_get filtering.enabled || echo 0)" + log_requests="$(uci_get filtering.log_requests || echo 1)" + filter_cdn="$(uci_get filtering.filter_cdn || echo 0)" + filter_media="$(uci_get filtering.filter_media || echo 0)" + block_ads="$(uci_get filtering.block_ads || echo 0)" + addon_script="$(uci_get filtering.addon_script || echo /etc/mitmproxy/addons/secubox_filter.py)" } ensure_dir() { [ -d "$1" ] || mkdir -p "$1"; } @@ -69,6 +94,10 @@ has_lxc() { command -v lxc-stop >/dev/null 2>&1 } +has_nft() { + command -v nft >/dev/null 2>&1 +} + # Ensure required packages are installed ensure_packages() { require_root @@ -83,6 +112,128 @@ ensure_packages() { done } +# ============================================================================= +# NFTABLES TRANSPARENT MODE FUNCTIONS +# ============================================================================= + +nft_setup() { + load_config + require_root + + if ! has_nft; then + log_error "nftables not available" + return 1 + fi + + if [ "$mode" != "transparent" ]; then + log_warn "Proxy mode is '$mode', not 'transparent'. Firewall rules not needed." + return 0 + fi + + log_info "Setting up nftables for transparent proxy..." + + # Create mitmproxy table + nft add table inet $NFT_TABLE 2>/dev/null || true + + # Create chains + nft add chain inet $NFT_TABLE prerouting { type nat hook prerouting priority -100 \; } 2>/dev/null || true + nft add chain inet $NFT_TABLE output { type nat hook output priority -100 \; } 2>/dev/null || true + + # Create bypass set for whitelisted IPs + nft add set inet $NFT_TABLE bypass_ipv4 { type ipv4_addr \; flags interval \; } 2>/dev/null || true + nft add set inet $NFT_TABLE bypass_ipv6 { type ipv6_addr \; flags interval \; } 2>/dev/null || true + + # Load whitelist IPs into bypass set + if [ "$whitelist_enabled" = "1" ]; then + local bypass_ips=$(uci_get_list whitelist.bypass_ip 2>/dev/null) + for ip in $bypass_ips; do + case "$ip" in + *:*) nft add element inet $NFT_TABLE bypass_ipv6 { $ip } 2>/dev/null || true ;; + *) nft add element inet $NFT_TABLE bypass_ipv4 { $ip } 2>/dev/null || true ;; + esac + done + log_info "Loaded whitelist bypass IPs" + fi + + # Get interface index if specified + local iif_match="" + if [ -n "$transparent_iface" ]; then + iif_match="iifname \"$transparent_iface\"" + fi + + # Flush existing rules in our chains + nft flush chain inet $NFT_TABLE prerouting 2>/dev/null || true + nft flush chain inet $NFT_TABLE output 2>/dev/null || true + + # Add bypass rules first (before redirect) + nft add rule inet $NFT_TABLE prerouting ip daddr @bypass_ipv4 return 2>/dev/null || true + nft add rule inet $NFT_TABLE prerouting ip6 daddr @bypass_ipv6 return 2>/dev/null || true + + # Don't intercept traffic from the proxy itself + nft add rule inet $NFT_TABLE prerouting meta skuid mitmproxy return 2>/dev/null || true + + # Redirect HTTP traffic + if [ "$redirect_http" = "1" ]; then + if [ -n "$iif_match" ]; then + nft add rule inet $NFT_TABLE prerouting $iif_match tcp dport $http_port redirect to :$proxy_port + else + nft add rule inet $NFT_TABLE prerouting tcp dport $http_port redirect to :$proxy_port + fi + log_info "HTTP redirect: port $http_port -> $proxy_port" + fi + + # Redirect HTTPS traffic + if [ "$redirect_https" = "1" ]; then + if [ -n "$iif_match" ]; then + nft add rule inet $NFT_TABLE prerouting $iif_match tcp dport $https_port redirect to :$proxy_port + else + nft add rule inet $NFT_TABLE prerouting tcp dport $https_port redirect to :$proxy_port + fi + log_info "HTTPS redirect: port $https_port -> $proxy_port" + fi + + log_info "nftables transparent mode rules applied" + log_info "Table: inet $NFT_TABLE" +} + +nft_teardown() { + require_root + + if ! has_nft; then + return 0 + fi + + log_info "Removing nftables transparent mode rules..." + + # Delete the entire table (removes all chains and rules) + nft delete table inet $NFT_TABLE 2>/dev/null || true + + log_info "nftables rules removed" +} + +nft_status() { + if ! has_nft; then + echo "nftables not available" + return 1 + fi + + echo "=== mitmproxy nftables rules ===" + if nft list table inet $NFT_TABLE 2>/dev/null; then + echo "" + echo "Bypass IPv4 set:" + nft list set inet $NFT_TABLE bypass_ipv4 2>/dev/null || echo " (empty or not created)" + echo "" + echo "Bypass IPv6 set:" + nft list set inet $NFT_TABLE bypass_ipv6 2>/dev/null || echo " (empty or not created)" + else + echo "No mitmproxy rules configured" + fi +} + +# ============================================================================= +# LXC CONTAINER FUNCTIONS +# ============================================================================= + lxc_check_prereqs() { log_info "Checking LXC prerequisites..." ensure_packages lxc lxc-common lxc-attach lxc-start lxc-stop lxc-destroy || return 1 @@ -171,7 +322,7 @@ apk add --no-cache \ pip3 install --break-system-packages mitmproxy # Create directories -mkdir -p /data /var/log/mitmproxy +mkdir -p /data /var/log/mitmproxy /etc/mitmproxy/addons # Create startup script cat > /opt/start-mitmproxy.sh << 'START' @@ -183,6 +334,8 @@ MODE="${MITMPROXY_MODE:-regular}" PROXY_PORT="${MITMPROXY_PROXY_PORT:-8080}" WEB_PORT="${MITMPROXY_WEB_PORT:-8081}" WEB_HOST="${MITMPROXY_WEB_HOST:-0.0.0.0}" +ADDON_SCRIPT="${MITMPROXY_ADDON_SCRIPT:-}" +FILTERING_ENABLED="${MITMPROXY_FILTERING_ENABLED:-0}" # Build command arguments ARGS="--listen-host 0.0.0.0 --listen-port $PROXY_PORT" @@ -207,6 +360,12 @@ esac [ "$ANTICOMP" = "1" ] && ARGS="$ARGS --anticomp" [ -n "$FLOW_DETAIL" ] && ARGS="$ARGS --flow-detail $FLOW_DETAIL" +# Load addon script if filtering is enabled +if [ "$FILTERING_ENABLED" = "1" ] && [ -n "$ADDON_SCRIPT" ] && [ -f "$ADDON_SCRIPT" ]; then + ARGS="$ARGS -s $ADDON_SCRIPT" + echo "Loading addon: $ADDON_SCRIPT" +fi + # Run mitmweb (web interface + proxy) exec mitmweb $ARGS --web-host "$WEB_HOST" --web-port "$WEB_PORT" --no-web-open-browser START @@ -225,11 +384,174 @@ SETUP } rm -f "$rootfs/tmp/setup-mitmproxy.sh" + + # Install the SecuBox filter addon + install_addon_script +} + +install_addon_script() { + load_config + ensure_dir "$ADDON_PATH" + ensure_dir "$LXC_ROOTFS/etc/mitmproxy/addons" + + # Create the SecuBox filter addon + cat > "$ADDON_PATH/secubox_filter.py" << 'ADDON' +""" +SecuBox mitmproxy Filter Addon +CDN/MediaFlow filtering and request logging +""" +import json +import os +import re +from datetime import datetime +from mitmproxy import http, ctx + +# CDN domains to track +CDN_DOMAINS = [ + r'\.cloudflare\.com$', + r'\.cloudflareinsights\.com$', + r'\.akamai\.net$', + r'\.akamaized\.net$', + r'\.fastly\.net$', + r'\.cloudfront\.net$', + r'\.azureedge\.net$', + r'\.jsdelivr\.net$', + r'\.unpkg\.com$', + r'\.cdnjs\.cloudflare\.com$', +] + +# Media streaming domains +MEDIA_DOMAINS = [ + r'\.googlevideo\.com$', + r'\.youtube\.com$', + r'\.ytimg\.com$', + r'\.netflix\.com$', + r'\.nflxvideo\.net$', + r'\.spotify\.com$', + r'\.scdn\.co$', + r'\.twitch\.tv$', + r'\.ttvnw\.net$', +] + +# Ad/Tracker domains to block +AD_DOMAINS = [ + r'\.doubleclick\.net$', + r'\.googlesyndication\.com$', + r'\.googleadservices\.com$', + r'\.facebook\.net$', + r'\.analytics\.google\.com$', + r'\.google-analytics\.com$', + r'\.hotjar\.com$', + r'\.segment\.io$', + r'\.mixpanel\.com$', + r'\.amplitude\.com$', +] + +class SecuBoxFilter: + def __init__(self): + self.log_file = os.environ.get('MITMPROXY_LOG_FILE', '/data/requests.log') + self.filter_cdn = os.environ.get('MITMPROXY_FILTER_CDN', '0') == '1' + self.filter_media = os.environ.get('MITMPROXY_FILTER_MEDIA', '0') == '1' + self.block_ads = os.environ.get('MITMPROXY_BLOCK_ADS', '0') == '1' + self.log_requests = os.environ.get('MITMPROXY_LOG_REQUESTS', '1') == '1' + + ctx.log.info(f"SecuBox Filter initialized") + ctx.log.info(f" Log requests: {self.log_requests}") + ctx.log.info(f" Filter CDN: {self.filter_cdn}") + ctx.log.info(f" Filter Media: {self.filter_media}") + ctx.log.info(f" Block Ads: {self.block_ads}") + + def _match_domain(self, host, patterns): + """Check if host matches any pattern""" + for pattern in patterns: + if re.search(pattern, host, re.IGNORECASE): + return True + return False + + def _log_request(self, flow: http.HTTPFlow, category: str = "normal"): + """Log request to JSON file""" + if not self.log_requests: + return + + try: + entry = { + "timestamp": datetime.now().isoformat(), + "category": category, + "request": { + "method": flow.request.method, + "host": flow.request.host, + "port": flow.request.port, + "path": flow.request.path, + "scheme": flow.request.scheme, + }, + } + + if flow.response: + entry["response"] = { + "status_code": flow.response.status_code, + "content_type": flow.response.headers.get("content-type", ""), + "content_length": len(flow.response.content) if flow.response.content else 0, + } + + with open(self.log_file, 'a') as f: + f.write(json.dumps(entry) + '\n') + except Exception as e: + ctx.log.error(f"Failed to log request: {e}") + + def request(self, flow: http.HTTPFlow): + """Process incoming request""" + host = flow.request.host + + # Check for ad/tracker domains + if self.block_ads and self._match_domain(host, AD_DOMAINS): + ctx.log.info(f"Blocked ad/tracker: {host}") + flow.response = http.Response.make( + 403, + b"Blocked by SecuBox", + {"Content-Type": "text/plain"} + ) + self._log_request(flow, "blocked_ad") + return + + # Track CDN requests + if self._match_domain(host, CDN_DOMAINS): + self._log_request(flow, "cdn") + if self.filter_cdn: + ctx.log.info(f"CDN request: {host}{flow.request.path[:50]}") + return + + # Track media requests + if self._match_domain(host, MEDIA_DOMAINS): + self._log_request(flow, "media") + if self.filter_media: + ctx.log.info(f"Media request: {host}{flow.request.path[:50]}") + return + + # Log normal request + self._log_request(flow, "normal") + + def response(self, flow: http.HTTPFlow): + """Process response - update log entry if needed""" + pass + +addons = [SecuBoxFilter()] +ADDON + + # Copy to container rootfs + cp "$ADDON_PATH/secubox_filter.py" "$LXC_ROOTFS/etc/mitmproxy/addons/" 2>/dev/null || true + + log_info "Addon script installed: $ADDON_PATH/secubox_filter.py" } lxc_create_config() { load_config + # Build addon path for container + local container_addon="" + if [ "$filtering_enabled" = "1" ] && [ -f "$LXC_ROOTFS$addon_script" ]; then + container_addon="$addon_script" + fi + cat > "$LXC_CONFIG" << EOF # mitmproxy LXC Configuration lxc.uts.name = $LXC_NAME @@ -243,6 +565,7 @@ lxc.net.0.type = none # Mounts lxc.mount.auto = proc:mixed sys:ro cgroup:mixed lxc.mount.entry = $data_path data none bind,create=dir 0 0 +lxc.mount.entry = $ADDON_PATH etc/mitmproxy/addons none bind,create=dir 0 0 # Environment variables for configuration lxc.environment = MITMPROXY_MODE=$mode @@ -255,6 +578,13 @@ lxc.environment = SSL_INSECURE=$ssl_insecure lxc.environment = ANTICACHE=$anticache lxc.environment = ANTICOMP=$anticomp lxc.environment = FLOW_DETAIL=$flow_detail +lxc.environment = MITMPROXY_FILTERING_ENABLED=$filtering_enabled +lxc.environment = MITMPROXY_ADDON_SCRIPT=$addon_script +lxc.environment = MITMPROXY_LOG_REQUESTS=$log_requests +lxc.environment = MITMPROXY_FILTER_CDN=$filter_cdn +lxc.environment = MITMPROXY_FILTER_MEDIA=$filter_media +lxc.environment = MITMPROXY_BLOCK_ADS=$block_ads +lxc.environment = MITMPROXY_LOG_FILE=/data/requests.log # Capabilities lxc.cap.drop = sys_admin sys_module mac_admin mac_override @@ -293,19 +623,44 @@ lxc_run() { # Ensure mount points exist ensure_dir "$data_path" + ensure_dir "$ADDON_PATH" + + # Setup firewall rules if in transparent mode + if [ "$mode" = "transparent" ]; then + nft_setup + fi log_info "Starting mitmproxy LXC container..." + log_info "Mode: $mode" log_info "Web interface: http://0.0.0.0:$web_port" log_info "Proxy port: $proxy_port" + [ "$filtering_enabled" = "1" ] && log_info "Filtering: enabled" exec lxc-start -n "$LXC_NAME" -F -f "$LXC_CONFIG" } lxc_status() { + load_config + echo "=== mitmproxy Status ===" + echo "" + if lxc-info -n "$LXC_NAME" >/dev/null 2>&1; then lxc-info -n "$LXC_NAME" else echo "LXC container '$LXC_NAME' not found or not configured" fi + + echo "" + echo "=== Configuration ===" + echo "Mode: $mode" + echo "Proxy port: $proxy_port" + echo "Web port: $web_port" + echo "Data path: $data_path" + echo "Filtering: $([ "$filtering_enabled" = "1" ] && echo "enabled" || echo "disabled")" + + if [ "$mode" = "transparent" ]; then + echo "" + nft_status + fi } lxc_logs() { @@ -342,6 +697,10 @@ lxc_destroy() { fi } +# ============================================================================= +# COMMANDS +# ============================================================================= + cmd_install() { require_root load_config @@ -355,11 +714,12 @@ cmd_install() { # Create directories ensure_dir "$data_path" + ensure_dir "$ADDON_PATH" lxc_check_prereqs || exit 1 lxc_create_rootfs || exit 1 - uci_set enabled '1' + uci_set main.enabled '1' /etc/init.d/mitmproxy enable log_info "mitmproxy installed." @@ -378,6 +738,12 @@ cmd_check() { else log_warn "LXC: not available" fi + + if has_nft; then + log_info "nftables: available" + else + log_warn "nftables: not available (needed for transparent mode)" + fi } cmd_update() { @@ -429,6 +795,14 @@ cmd_cert() { fi } +cmd_firewall_setup() { + nft_setup +} + +cmd_firewall_clear() { + nft_teardown +} + cmd_service_run() { require_root load_config @@ -444,6 +818,13 @@ cmd_service_run() { cmd_service_stop() { require_root + load_config + + # Remove firewall rules + if [ "$mode" = "transparent" ]; then + nft_teardown + fi + lxc_stop } @@ -456,6 +837,8 @@ case "${1:-}" in logs) shift; cmd_logs "$@" ;; shell) shift; cmd_shell "$@" ;; cert) shift; cmd_cert "$@" ;; + firewall-setup) shift; cmd_firewall_setup "$@" ;; + firewall-clear) shift; cmd_firewall_clear "$@" ;; service-run) shift; cmd_service_run "$@" ;; service-stop) shift; cmd_service_stop "$@" ;; help|--help|-h|'') usage ;;