From 1dd0c95a090a00a891d20899889e001e8e6ddb00 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Sat, 17 Jan 2026 08:49:59 +0100 Subject: [PATCH] feat(mitmproxy): Add embedded Web UI view with token auth - Add get_web_token RPCD method to retrieve auth token - Create webui.js view that embeds mitmweb in an iframe - Capture auth token at startup and save to file - Add Web UI navigation to all mitmproxy views - Fix PATH for /usr/local/bin in Docker image - Change default port from 8080 to 8888 (avoid CrowdSec conflict) secubox-app-mitmproxy: bump to r12 luci-app-mitmproxy: bump to r2 Co-Authored-By: Claude Opus 4.5 --- package/secubox/luci-app-mitmproxy/Makefile | 2 +- .../luci-static/resources/mitmproxy/api.js | 11 ++ .../resources/view/mitmproxy/dashboard.js | 1 + .../resources/view/mitmproxy/requests.js | 1 + .../resources/view/mitmproxy/settings.js | 1 + .../resources/view/mitmproxy/webui.js | 130 ++++++++++++++++++ .../root/usr/libexec/rpcd/luci.mitmproxy | 23 ++++ .../share/luci/menu.d/luci-app-mitmproxy.json | 8 ++ .../secubox/secubox-app-mitmproxy/Makefile | 2 +- .../files/usr/sbin/mitmproxyctl | 16 ++- 10 files changed, 190 insertions(+), 5 deletions(-) create mode 100644 package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/webui.js diff --git a/package/secubox/luci-app-mitmproxy/Makefile b/package/secubox/luci-app-mitmproxy/Makefile index bfd2440f..5126c585 100644 --- a/package/secubox/luci-app-mitmproxy/Makefile +++ b/package/secubox/luci-app-mitmproxy/Makefile @@ -8,7 +8,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-mitmproxy PKG_VERSION:=0.4.0 -PKG_RELEASE:=1 +PKG_RELEASE:=2 PKG_ARCH:=all PKG_LICENSE:=Apache-2.0 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 9106f269..6482edd6 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 @@ -54,6 +54,11 @@ var callGetCaInfo = rpc.declare({ method: 'get_ca_info' }); +var callGetWebToken = rpc.declare({ + object: 'luci.mitmproxy', + method: 'get_web_token' +}); + var callServiceStart = rpc.declare({ object: 'luci.mitmproxy', method: 'service_start' @@ -170,6 +175,12 @@ return baseclass.extend({ }); }, + getWebToken: function() { + return callGetWebToken().catch(function() { + return { token: '', web_url: '', web_url_with_token: '' }; + }); + }, + serviceStart: function() { return callServiceStart(); }, diff --git a/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/dashboard.js b/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/dashboard.js index 915faac2..1e9397f2 100644 --- a/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/dashboard.js +++ b/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/dashboard.js @@ -14,6 +14,7 @@ Theme.init({ language: lang }); var MITMPROXY_NAV = [ { id: 'dashboard', icon: '📊', label: 'Dashboard' }, + { id: 'webui', icon: '🖥️', label: 'Web UI' }, { id: 'requests', icon: '🔍', label: 'Requests' }, { id: 'settings', icon: '⚙️', label: 'Settings' } ]; diff --git a/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/requests.js b/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/requests.js index 0b2641ed..e2e4d5b1 100644 --- a/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/requests.js +++ b/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/requests.js @@ -14,6 +14,7 @@ Theme.init({ language: lang }); var MITMPROXY_NAV = [ { id: 'dashboard', icon: '📊', label: 'Dashboard' }, + { id: 'webui', icon: '🖥️', label: 'Web UI' }, { id: 'requests', icon: '🔍', label: 'Requests' }, { id: 'settings', icon: '⚙️', label: 'Settings' } ]; 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 e51d0e93..c21ce23c 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 @@ -13,6 +13,7 @@ Theme.init({ language: lang }); var MITMPROXY_NAV = [ { id: 'dashboard', icon: '📊', label: 'Dashboard' }, + { id: 'webui', icon: '🖥️', label: 'Web UI' }, { id: 'requests', icon: '🔍', label: 'Requests' }, { id: 'settings', icon: '⚙️', label: 'Settings' } ]; diff --git a/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/webui.js b/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/webui.js new file mode 100644 index 00000000..dff6c8a5 --- /dev/null +++ b/package/secubox/luci-app-mitmproxy/htdocs/luci-static/resources/view/mitmproxy/webui.js @@ -0,0 +1,130 @@ +'use strict'; +'require view'; +'require dom'; +'require ui'; +'require mitmproxy.api as api'; +'require secubox-theme/theme as Theme'; +'require secubox-portal/header as SbHeader'; + +var lang = (typeof L !== 'undefined' && L.env && L.env.lang) || + (document.documentElement && document.documentElement.getAttribute('lang')) || + (navigator.language ? navigator.language.split('-')[0] : 'en'); +Theme.init({ language: lang }); + +var MITMPROXY_NAV = [ + { id: 'dashboard', icon: '📊', label: 'Dashboard' }, + { id: 'webui', icon: '🖥️', label: 'Web UI' }, + { id: 'requests', icon: '🔍', label: 'Requests' }, + { id: 'settings', icon: '⚙️', label: 'Settings' } +]; + +function renderMitmproxyNav(activeId) { + return E('div', { + 'class': 'mp-app-nav', + 'style': 'display:flex;gap:8px;margin-bottom:20px;padding:12px 16px;background:#141419;border:1px solid rgba(255,255,255,0.08);border-radius:12px;' + }, MITMPROXY_NAV.map(function(item) { + var isActive = activeId === item.id; + return E('a', { + 'href': L.url('admin', 'secubox', 'security', 'mitmproxy', item.id), + 'style': 'display:flex;align-items:center;gap:8px;padding:10px 16px;border-radius:8px;text-decoration:none;font-size:14px;font-weight:500;transition:all 0.2s;' + + (isActive ? 'background:linear-gradient(135deg,#e74c3c,#c0392b);color:white;' : 'color:#a0a0b0;background:transparent;') + }, [ + E('span', {}, item.icon), + E('span', {}, _(item.label)) + ]); + })); +} + +return view.extend({ + title: _('mitmproxy Web UI'), + + load: function() { + return Promise.all([ + api.getStatus(), + api.getWebToken() + ]); + }, + + render: function(data) { + var status = data[0] || {}; + var tokenData = data[1] || {}; + + var content; + + if (!status.running) { + content = E('div', { 'class': 'mp-card', 'style': 'text-align: center; padding: 60px 20px;' }, [ + E('div', { 'style': 'font-size: 64px; margin-bottom: 20px;' }, '⚠️'), + E('h2', { 'style': 'margin: 0 0 10px 0; color: #f39c12;' }, _('mitmproxy is not running')), + E('p', { 'style': 'color: #a0a0b0; margin: 0 0 20px 0;' }, _('Start the service to access the Web UI')), + E('button', { + 'class': 'mp-btn mp-btn-success', + 'click': function() { + ui.showModal(_('Starting...'), [ + E('p', { 'class': 'spinning' }, _('Starting mitmproxy...')) + ]); + api.serviceStart().then(function() { + ui.hideModal(); + setTimeout(function() { location.reload(); }, 2000); + }); + } + }, '▶ Start mitmproxy') + ]); + } else if (!tokenData.token) { + content = E('div', { 'class': 'mp-card', 'style': 'text-align: center; padding: 60px 20px;' }, [ + E('div', { 'style': 'font-size: 64px; margin-bottom: 20px;' }, '🔄'), + E('h2', { 'style': 'margin: 0 0 10px 0; color: #3498db;' }, _('Waiting for authentication token')), + E('p', { 'style': 'color: #a0a0b0; margin: 0 0 20px 0;' }, _('The service is starting. Please wait or refresh the page.')), + E('button', { + 'class': 'mp-btn mp-btn-primary', + 'click': function() { location.reload(); } + }, '🔄 Refresh') + ]); + } else { + var iframeSrc = tokenData.web_url_with_token; + + content = E('div', { 'style': 'display: flex; flex-direction: column; height: calc(100vh - 200px); min-height: 600px;' }, [ + // Toolbar + E('div', { 'style': 'display: flex; align-items: center; gap: 12px; margin-bottom: 12px; padding: 12px 16px; background: #141419; border-radius: 8px;' }, [ + E('span', { 'style': 'color: #27ae60; font-weight: 500;' }, '● Connected'), + E('span', { 'style': 'color: #a0a0b0; font-size: 13px;' }, tokenData.web_url), + E('div', { 'style': 'flex: 1;' }), + E('button', { + 'class': 'mp-btn', + 'click': function() { + var iframe = document.querySelector('.mitmproxy-iframe'); + if (iframe) iframe.src = iframe.src; + } + }, '🔄 Refresh'), + E('a', { + 'class': 'mp-btn mp-btn-secondary', + 'href': iframeSrc, + 'target': '_blank' + }, '↗ Open in New Tab') + ]), + + // Iframe container + E('div', { + 'style': 'flex: 1; border-radius: 8px; overflow: hidden; border: 1px solid rgba(255,255,255,0.1);' + }, [ + E('iframe', { + 'class': 'mitmproxy-iframe', + 'src': iframeSrc, + 'style': 'width: 100%; height: 100%; border: none; background: #1a1a1f;', + 'allow': 'fullscreen', + 'sandbox': 'allow-same-origin allow-scripts allow-forms allow-popups allow-modals' + }) + ]) + ]); + } + + var wrapper = E('div', { 'class': 'secubox-page-wrapper' }); + wrapper.appendChild(SbHeader.render()); + wrapper.appendChild(renderMitmproxyNav('webui')); + wrapper.appendChild(content); + return wrapper; + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); 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 7be91afc..198dee66 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 @@ -400,6 +400,25 @@ EOF fi } +get_web_token() { + local token_file="$LXC_ROOTFS/data/.mitmproxy_token" + local router_ip=$(uci -q get network.lan.ipaddr || echo "192.168.1.1") + local web_port=$(uci -q get mitmproxy.main.web_port || echo "8081") + local token="" + + if [ -f "$token_file" ]; then + token=$(cat "$token_file" 2>/dev/null) + fi + + cat < diff --git a/package/secubox/secubox-app-mitmproxy/files/usr/sbin/mitmproxyctl b/package/secubox/secubox-app-mitmproxy/files/usr/sbin/mitmproxyctl index 1d80125b..25cd982c 100755 --- a/package/secubox/secubox-app-mitmproxy/files/usr/sbin/mitmproxyctl +++ b/package/secubox/secubox-app-mitmproxy/files/usr/sbin/mitmproxyctl @@ -317,6 +317,7 @@ lxc_create_docker_rootfs() { # Create startup script for mitmweb cat > "$rootfs/opt/start-mitmproxy.sh" << 'START' #!/bin/sh +export PATH="/usr/local/bin:$PATH" cd /data # Read environment variables for configuration @@ -356,9 +357,18 @@ if [ "$FILTERING_ENABLED" = "1" ] && [ -n "$ADDON_SCRIPT" ] && [ -f "$ADDON_SCRI echo "Loading addon: $ADDON_SCRIPT" fi -# Run mitmweb (web interface + proxy) -# Disable web authentication for LAN access -exec mitmweb $ARGS --web-host "$WEB_HOST" --web-port "$WEB_PORT" --no-web-open-browser --set web_password= +# Run mitmweb and capture token +# The token is printed to stderr, capture it and save to file +mitmweb $ARGS --web-host "$WEB_HOST" --web-port "$WEB_PORT" --no-web-open-browser 2>&1 | while IFS= read -r line; do + echo "$line" + # Extract and save token if present + case "$line" in + *"token="*) + token=$(echo "$line" | sed -n 's/.*token=\([a-f0-9]*\).*/\1/p') + [ -n "$token" ] && echo "$token" > /data/.mitmproxy_token + ;; + esac +done START chmod +x "$rootfs/opt/start-mitmproxy.sh"