commit 97dc5a35cf0ce73b924b2a9621b7e855039a246b Author: agent Date: Tue Jun 2 21:08:30 2026 +0200 feat: scaffold inicial de web_proxy (proxy de interceptacion liviano) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..742cf98 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Estado local y capturas — no se versionan +local_files/ +captures/ +*.mitm +*.har +*.pid +*.log +chromium-profile/ diff --git a/app.md b/app.md new file mode 100644 index 0000000..7c012b7 --- /dev/null +++ b/app.md @@ -0,0 +1,161 @@ +--- +name: web_proxy +lang: bash +domain: cybersecurity +version: 0.1.0 +description: "Proxy de interceptacion HTTP/HTTPS liviano y siempre activo (mitmproxy) para capturar todo el trafico de un navegador con rotacion temporal de capturas, alternativa ligera a ZAP/Burp." +tags: [service, web-proxy, mitmproxy, proxy, capture, cybersecurity, chromium] +icon: + phosphor: "shield-check" + accent: "#7c3aed" +uses_functions: + - start_mitm_capture_bash_cybersecurity + - query_mitm_flows_bash_cybersecurity + - rotate_capture_flows_py_cybersecurity + - launch_chromium_proxy_bash_browser + - port_kill_bash_infra +uses_types: [] +framework: "" +entry_point: "web_proxy" +dir_path: "apps/web_proxy" +repo_url: "" +service: + port: 8080 + health_endpoint: null + health_timeout_s: 3 + systemd_unit: web_proxy.service + systemd_scope: user + restart_policy: always + runtime: systemd-user + pc_targets: + - home-wsl + is_local_only: true +e2e_checks: + - id: syntax + cmd: "bash -n web_proxy" + timeout_s: 10 + - id: help + cmd: "./web_proxy help" + expect_stdout_contains: "proxy de interceptacion" + timeout_s: 10 + - id: capture_roundtrip + cmd: "bash tests/e2e_capture.sh" + expect_stdout_contains: "CAPTURE_OK" + timeout_s: 40 +--- + +# web_proxy + +Proxy de interceptacion HTTP/HTTPS liviano y siempre activo, pensado como +alternativa ligera a OWASP ZAP y Burp Suite. Construido sobre `mitmproxy` +(footprint de decenas de MB frente a la JVM de ZAP/Burp) y compuesto a partir +de funciones del registry `fn_registry`. + +## Que hace + +- Levanta un proxy local (`mitmdump` headless) que registra **todo** el trafico + HTTP/HTTPS que pasa por el. +- **Rota las capturas** en archivos `.mitm` por ventanas de tiempo configurables + (default 20 minutos), de modo que puedas consultar el trafico de cada tramo + por separado. +- Corre **siempre activo** como servicio `systemd --user` con `Restart=always`, + o de forma manual con `start`/`stop`. +- Lanza un **Chromium proxeado** en un perfil aislado para no contaminar tu + navegacion normal. +- Permite **consultar** las capturas guardadas: volcado filtrado, exportacion a + HAR, o inspeccion visual en `mitmweb`. + +## Ejemplo + +```bash +cd apps/web_proxy + +# Servicio siempre activo en el puerto 8080, capturas en ~/captures, rotando cada 20 min +./web_proxy install-service --port 8080 --out ~/captures --rotate-min 20 + +# Confiar en el CA de mitmproxy para que HTTPS no rompa +./web_proxy ca + +# Navegar con todo el trafico capturado (Chromium en perfil aislado) +./web_proxy browser --url https://example.com + +# Consultar la ultima ventana de captura +./web_proxy query "~m POST" # solo peticiones POST +./web_proxy query "~c 500" --all # respuestas 5xx en todas las capturas +./web_proxy har ~/captures/sesion.har + +# Inspeccion visual en el navegador +./web_proxy inspect + +# Estado y logs +./web_proxy status +./web_proxy logs +``` + +Modo manual (sin servicio): + +```bash +./web_proxy start --port 8080 --out ~/captures --rotate-min 20 +./web_proxy browser +./web_proxy query "~d api.github.com" +./web_proxy stop +``` + +## Cuando usarla + +Cuando quieras registrar de forma continua y liviana todo lo que hace un +navegador (debug de APIs, auditoria de trafico, inspeccion de requests de una +SPA) sin arrancar la maquinaria pesada de ZAP o Burp, y poder volver a consultar +sesiones pasadas troceadas por ventanas de tiempo. + +## Como consultar las capturas + +Las capturas son archivos `.mitm` en el directorio de salida (`~/captures` por +defecto), con nombre `traffic-YYYYmmdd-HHMMSS.mitm`, uno por ventana de rotacion. + +| Forma | Comando | +|---|---| +| Volcado filtrado (CLI) | `./web_proxy query "~m POST"` | +| Volcado de todas las ventanas | `./web_proxy query "~u /api" --all` | +| Exportar a HAR | `./web_proxy har salida.har` | +| Inspeccion visual (UI web) | `./web_proxy inspect [archivo.mitm]` | +| Directo con mitmproxy | `mitmproxy -r ~/captures/traffic-*.mitm` | + +Sintaxis de filtros de mitmproxy: `~u` URL, `~d` dominio, `~m` metodo, `~c` +codigo de estado, `~bq`/`~bs` cuerpo de peticion/respuesta. Combinables con +`&` (y), `|` (o), `!` (no). + +## Gotchas + +- **HTTPS**: requiere confiar en el CA de mitmproxy. Ejecuta `./web_proxy ca` + para las instrucciones. El navegador que lanza `./web_proxy browser` usa + `--ignore-certificate-errors` si no instalas el CA, lo cual es comodo pero + menos seguro (vale para uso local de captura, no para navegacion sensible). +- **Durabilidad**: el addon de rotacion hace `flush()` tras cada flujo, asi que + las capturas sobreviven a un `kill -9` o un corte abrupto del proceso. +- **Rotacion por trafico**: la rotacion ocurre al recibir un flujo; si no hay + trafico durante la ventana, el archivo no rota hasta el siguiente request. +- **Persistencia tras logout**: el servicio `systemd --user` para al cerrar + sesion salvo que actives linger con `./web_proxy install-service --linger`. +- **Conflicto de puerto**: `start` y el servicio no pueden coexistir en el mismo + puerto. `install-service` para el proxy manual automaticamente antes de + arrancar el servicio. +- **Stop fiable**: el `stop` mata por PID guardado en el pidfile (no depende de + `lsof`/`port_kill`, que en este entorno no siempre detecta el listener a + tiempo). + +## Arquitectura + +Orquesta funciones del registry (no reimplementa logica reutilizable): + +| Funcion del registry | Rol | +|---|---| +| `start_mitm_capture_bash_cybersecurity` | Arranca `mitmdump` headless con rotacion | +| `rotate_capture_flows_py_cybersecurity` | Addon de mitmproxy que trocea las capturas | +| `query_mitm_flows_bash_cybersecurity` | Consulta capturas guardadas (volcado / HAR) | +| `launch_chromium_proxy_bash_browser` | Lanza Chromium proxeado en perfil aislado | +| `port_kill_bash_infra` | Limpieza de puertos ocupados | + +El CLI `web_proxy` aporta solo la logica especifica de la app: gestion del +pidfile, configuracion persistente, generacion del unit `systemd --user` y la +interfaz de subcomandos. diff --git a/tests/e2e_capture.sh b/tests/e2e_capture.sh new file mode 100755 index 0000000..c64a9e4 --- /dev/null +++ b/tests/e2e_capture.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# +# e2e_capture.sh — prueba de roundtrip completa del stack de captura. +# +# Levanta un servidor HTTP local, arranca el proxy mitmproxy a traves de las +# funciones del registry, hace pasar trafico por el, y verifica que: +# - se crea un archivo .mitm con contenido, +# - la captura sobrevive a un SIGKILL (flush por flujo), +# - la consulta a HAR devuelve las entradas esperadas con sus codigos. +# +# Emite "CAPTURE_OK" en stdout si todo pasa; cualquier otro caso es fallo. +# Diseñado para ser idempotente y autolimpiante (puertos altos, dir temporal). + +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REGISTRY_ROOT="${FN_REGISTRY_ROOT:-$(cd "$SCRIPT_DIR/../../.." && pwd)}" +export FN_REGISTRY_ROOT="$REGISTRY_ROOT" + +START_FN="$REGISTRY_ROOT/bash/functions/cybersecurity/start_mitm_capture.sh" +QUERY_FN="$REGISTRY_ROOT/bash/functions/cybersecurity/query_mitm_flows.sh" + +PROXY_PORT=18906 +UPSTREAM_PORT=18913 +WORK="$(mktemp -d /tmp/web_proxy_e2e.XXXXXX)" + +cleanup() { + [[ -n "${PROXY_PID:-}" ]] && kill -9 "$PROXY_PID" 2>/dev/null + [[ -n "${SRV_PID:-}" ]] && kill -9 "$SRV_PID" 2>/dev/null + rm -rf "$WORK" +} +trap cleanup EXIT + +fail() { echo "CAPTURE_FAIL: $*" >&2; exit 1; } + +mkdir -p "$WORK/www" "$WORK/cap" +echo "web_proxy e2e" > "$WORK/www/index.html" + +# --- 1. Upstream HTTP local (respuesta valida garantizada) ------------------- +setsid bash -c "cd '$WORK/www' && exec python3 -m http.server $UPSTREAM_PORT" /dev/null 2>&1 & +SRV_PID=$! +disown "$SRV_PID" 2>/dev/null || true +sleep 1 + +# --- 2. Arrancar el proxy via funcion del registry --------------------------- +JSON="$(bash "$START_FN" --port "$PROXY_PORT" --out "$WORK/cap" --rotate-min 20)" \ + || fail "start_mitm_capture fallo" +PROXY_PID="$(printf '%s' "$JSON" | python3 -c 'import sys,json;print(json.load(sys.stdin)["pid"])' 2>/dev/null)" +[[ -n "$PROXY_PID" ]] || fail "no se obtuvo PID del proxy ($JSON)" +sleep 2 + +# --- 3. Trafico via proxy ---------------------------------------------------- +for path in / /index.html /alpha /beta; do + curl -s -x "http://127.0.0.1:$PROXY_PORT" "http://127.0.0.1:$UPSTREAM_PORT${path}" -o /dev/null \ + || fail "curl fallo en $path" +done +sleep 1 + +# --- 4. Verificar captura ---------------------------------------------------- +MITM="$(find "$WORK/cap" -name 'traffic-*.mitm' | head -1)" +[[ -n "$MITM" ]] || fail "no se creo archivo .mitm" +BYTES_BEFORE="$(stat -c%s "$MITM")" +[[ "$BYTES_BEFORE" -gt 0 ]] || fail "el .mitm quedo vacio antes de matar" + +# --- 5. Durabilidad: SIGKILL no debe perder datos ---------------------------- +kill -9 "$PROXY_PID" 2>/dev/null +PROXY_PID="" +sleep 1 +BYTES_AFTER="$(stat -c%s "$MITM")" +[[ "$BYTES_AFTER" -eq "$BYTES_BEFORE" && "$BYTES_AFTER" -gt 0 ]] \ + || fail "la captura no sobrevivio al SIGKILL ($BYTES_BEFORE -> $BYTES_AFTER)" + +# --- 6. Consulta a HAR ------------------------------------------------------- +bash "$QUERY_FN" "$MITM" --har "$WORK/out.har" >/dev/null 2>&1 || fail "export HAR fallo" +ENTRIES="$(python3 -c "import json;print(len(json.load(open('$WORK/out.har'))['log']['entries']))" 2>/dev/null)" +[[ "$ENTRIES" == "4" ]] || fail "HAR esperaba 4 entradas, obtuvo ${ENTRIES:-0}" + +echo "CAPTURE_OK ($ENTRIES flujos, ${BYTES_AFTER}B, sobrevivio SIGKILL)" diff --git a/web_proxy b/web_proxy new file mode 100755 index 0000000..d4733ac --- /dev/null +++ b/web_proxy @@ -0,0 +1,464 @@ +#!/usr/bin/env bash +# +# web_proxy — proxy de interceptacion HTTP/HTTPS liviano, siempre activo. +# +# Orquesta funciones del registry fn_registry para capturar todo el trafico de +# un navegador (estilo ZAP/Burp) con un footprint minimo basado en mitmproxy: +# +# start_mitm_capture_bash_cybersecurity arranca mitmdump con rotacion +# rotate_capture_flows_py_cybersecurity addon que trocea las capturas +# query_mitm_flows_bash_cybersecurity consulta capturas guardadas +# launch_chromium_proxy_bash_browser navegador proxeado en perfil aislado +# +# El proxy puede correr en dos modos: +# - manual: `web_proxy start` / `web_proxy stop` (background + pidfile) +# - servicio: `web_proxy install-service` (systemd --user, Restart=always, +# siempre activo, sobrevive a logout/reboot con linger) +# +# Estado y configuracion viven en $WEB_PROXY_HOME (default ~/.web_proxy). +# Las capturas .mitm viven en el directorio de salida (default ~/captures). + +set -uo pipefail + +# --- Resolucion de rutas ----------------------------------------------------- + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# apps/web_proxy/ -> raiz del registry son dos niveles arriba. +REGISTRY_ROOT="${FN_REGISTRY_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}" +export FN_REGISTRY_ROOT="$REGISTRY_ROOT" + +FN_BIN="$REGISTRY_ROOT/fn" +ADDON_PATH="$REGISTRY_ROOT/python/functions/cybersecurity/rotate_capture_flows.py" + +START_FN="$REGISTRY_ROOT/bash/functions/cybersecurity/start_mitm_capture.sh" +QUERY_FN="$REGISTRY_ROOT/bash/functions/cybersecurity/query_mitm_flows.sh" +BROWSER_FN="$REGISTRY_ROOT/bash/functions/browser/launch_chromium_proxy.sh" + +# --- Configuracion ----------------------------------------------------------- + +WEB_PROXY_HOME="${WEB_PROXY_HOME:-$HOME/.web_proxy}" +PIDFILE="$WEB_PROXY_HOME/web_proxy.pid" +CONFFILE="$WEB_PROXY_HOME/web_proxy.conf" +SERVICE_NAME="web_proxy.service" +SYSTEMD_USER_DIR="$HOME/.config/systemd/user" + +DEFAULT_PORT=8080 +DEFAULT_OUT="$HOME/captures" +DEFAULT_ROTATE=20 + +MITMDUMP_BIN="$(command -v mitmdump 2>/dev/null || echo "$HOME/.local/bin/mitmdump")" +MITMWEB_BIN="$(command -v mitmweb 2>/dev/null || echo "$HOME/.local/bin/mitmweb")" +CA_CERT="$HOME/.mitmproxy/mitmproxy-ca-cert.pem" + +mkdir -p "$WEB_PROXY_HOME" + +# --- Helpers ----------------------------------------------------------------- + +err() { printf '\033[31m%s\033[0m\n' "$*" >&2; } +ok() { printf '\033[32m%s\033[0m\n' "$*"; } +info() { printf '%s\n' "$*"; } + +# Lee KEY del conffile (formato KEY=VAL), o imprime el default pasado. +conf_get() { + local key="$1" def="${2:-}" + if [[ -f "$CONFFILE" ]]; then + local val + val="$(grep -E "^${key}=" "$CONFFILE" 2>/dev/null | tail -1 | cut -d= -f2-)" + [[ -n "$val" ]] && { printf '%s' "$val"; return; } + fi + printf '%s' "$def" +} + +conf_write() { + local port="$1" out="$2" rotate="$3" + cat > "$CONFFILE" </dev/null)" + [[ -n "$pid" ]] || return 1 + if kill -0 "$pid" 2>/dev/null; then + printf '%s' "$pid" + return 0 + fi + return 1 +} + +service_active() { + systemctl --user is-active --quiet "$SERVICE_NAME" 2>/dev/null +} + +# --- Subcomandos ------------------------------------------------------------- + +cmd_start() { + local port out rotate + port="$DEFAULT_PORT"; out="$DEFAULT_OUT"; rotate="$DEFAULT_ROTATE" + while [[ $# -gt 0 ]]; do + case "$1" in + --port) port="$2"; shift 2 ;; + --out) out="$2"; shift 2 ;; + --rotate-min) rotate="$2"; shift 2 ;; + *) err "start: flag desconocido: $1"; return 2 ;; + esac + done + + if service_active; then + err "El servicio systemd ($SERVICE_NAME) ya esta activo. Usa 'web_proxy stop-service' o gestiona el servicio en su lugar." + return 1 + fi + if running_pid >/dev/null; then + err "Ya hay un proxy manual corriendo (PID $(running_pid)). Para con 'web_proxy stop'." + return 1 + fi + + local json + json="$(bash "$START_FN" --port "$port" --out "$out" --rotate-min "$rotate")" || { + err "Fallo al arrancar mitmdump." + return 1 + } + local pid + pid="$(printf '%s' "$json" | python3 -c 'import sys,json;print(json.load(sys.stdin)["pid"])' 2>/dev/null)" + if [[ -z "$pid" ]]; then + err "No se pudo extraer el PID del arranque. Salida: $json" + return 1 + fi + printf '%s' "$pid" > "$PIDFILE" + conf_write "$port" "$out" "$rotate" + ok "Proxy activo en 127.0.0.1:$port (PID $pid)" + info " capturas -> $out (rotacion cada ${rotate} min)" + info " navegador -> web_proxy browser" + info " consultar -> web_proxy query \"~m POST\"" +} + +cmd_stop() { + local pid + if pid="$(running_pid)"; then + kill -TERM "$pid" 2>/dev/null + # Esperar cierre limpio; SIGKILL si se resiste. + for _ in 1 2 3 4 5; do + kill -0 "$pid" 2>/dev/null || break + sleep 0.3 + done + kill -9 "$pid" 2>/dev/null + rm -f "$PIDFILE" + ok "Proxy manual detenido (PID $pid)." + else + rm -f "$PIDFILE" + info "No habia proxy manual corriendo." + fi +} + +cmd_restart() { + cmd_stop + sleep 1 + cmd_start "$@" +} + +cmd_status() { + local port out rotate + port="$(conf_get PORT "$DEFAULT_PORT")" + out="$(conf_get OUT "$DEFAULT_OUT")" + rotate="$(conf_get ROTATE "$DEFAULT_ROTATE")" + + info "web_proxy — estado" + info " home: $WEB_PROXY_HOME" + info " capturas: $out" + info " rotacion: cada ${rotate} min" + + local pid + if pid="$(running_pid)"; then + ok " manual: ACTIVO (PID $pid, puerto $port)" + else + info " manual: parado" + fi + + if service_active; then + ok " servicio: ACTIVO ($SERVICE_NAME)" + elif systemctl --user list-unit-files "$SERVICE_NAME" &>/dev/null && \ + systemctl --user cat "$SERVICE_NAME" &>/dev/null; then + info " servicio: instalado, parado" + else + info " servicio: no instalado" + fi + + if [[ -d "$out" ]]; then + local n size + n="$(find "$out" -maxdepth 1 -name 'traffic-*.mitm' 2>/dev/null | wc -l)" + size="$(du -sh "$out" 2>/dev/null | cut -f1)" + info " archivos: ${n} capturas (${size:-0})" + local last + last="$(find "$out" -maxdepth 1 -name 'traffic-*.mitm' -printf '%T@ %p\n' 2>/dev/null | sort -nr | head -1 | cut -d' ' -f2-)" + [[ -n "$last" ]] && info " ultima: $(basename "$last")" + fi + + if [[ -f "$CA_CERT" ]]; then + info " CA HTTPS: $CA_CERT" + else + info " CA HTTPS: no generado todavia (arranca el proxy una vez)" + fi +} + +cmd_browser() { + local url="" proxy_port + proxy_port="$(conf_get PORT "$DEFAULT_PORT")" + while [[ $# -gt 0 ]]; do + case "$1" in + --url) url="$2"; shift 2 ;; + --port) proxy_port="$2"; shift 2 ;; + *) err "browser: flag desconocido: $1"; return 2 ;; + esac + done + + if ! running_pid >/dev/null && ! service_active; then + err "No hay proxy activo. Arranca primero con 'web_proxy start' o instala el servicio." + return 1 + fi + + local args=(--proxy "http://127.0.0.1:${proxy_port}" --profile "$WEB_PROXY_HOME/chromium-profile") + # Si el CA esta instalado en el perfil, no forzamos --ignore-certificate-errors. + [[ -n "$url" ]] && args+=(--url "$url") + bash "$BROWSER_FN" "${args[@]}" +} + +# Resuelve la lista de archivos a consultar: por defecto la ultima captura. +resolve_capture_files() { + local out scope="$1" + out="$(conf_get OUT "$DEFAULT_OUT")" + if [[ "$scope" == "all" ]]; then + find "$out" -maxdepth 1 -name 'traffic-*.mitm' 2>/dev/null | sort + else + find "$out" -maxdepth 1 -name 'traffic-*.mitm' -printf '%T@ %p\n' 2>/dev/null \ + | sort -nr | head -1 | cut -d' ' -f2- + fi +} + +cmd_query() { + local filter="" scope="last" + while [[ $# -gt 0 ]]; do + case "$1" in + --all) scope="all"; shift ;; + --last) scope="last"; shift ;; + *) filter="$1"; shift ;; + esac + done + local files + mapfile -t files < <(resolve_capture_files "$scope") + if [[ ${#files[@]} -eq 0 || -z "${files[0]}" ]]; then + err "No hay capturas en $(conf_get OUT "$DEFAULT_OUT")." + return 1 + fi + if [[ -n "$filter" ]]; then + bash "$QUERY_FN" "${files[@]}" --filter "$filter" + else + bash "$QUERY_FN" "${files[@]}" + fi +} + +cmd_har() { + local outhar="${1:-$HOME/captures/export.har}" scope="last" + [[ "${2:-}" == "--all" ]] && scope="all" + local files + mapfile -t files < <(resolve_capture_files "$scope") + if [[ ${#files[@]} -eq 0 || -z "${files[0]}" ]]; then + err "No hay capturas para exportar." + return 1 + fi + bash "$QUERY_FN" "${files[@]}" --har "$outhar" + ok "HAR exportado a $outhar" +} + +cmd_inspect() { + local file="${1:-}" + if [[ -z "$file" ]]; then + file="$(resolve_capture_files last)" + fi + if [[ -z "$file" || ! -f "$file" ]]; then + err "No hay captura para inspeccionar. Pasa una ruta o arranca el proxy." + return 1 + fi + info "Abriendo mitmweb sobre $(basename "$file") -> http://127.0.0.1:8081" + info "(Ctrl-C para cerrar la UI; no afecta al proxy activo)" + "$MITMWEB_BIN" -r "$file" --no-web-open-browser +} + +cmd_ca() { + info "HTTPS via proxy de interceptacion requiere confiar en el CA de mitmproxy." + info "" + if [[ ! -f "$CA_CERT" ]]; then + err "El CA aun no existe ($CA_CERT)." + info "Arranca el proxy una vez ('web_proxy start') para que mitmproxy lo genere." + return 1 + fi + info "CA generado en: $CA_CERT" + info "" + info "Opciones:" + info " 1. Confianza a nivel sistema (recomendado):" + info " sudo cp $CA_CERT /usr/local/share/ca-certificates/mitmproxy.crt" + info " sudo update-ca-certificates" + info " 2. Solo para el navegador proxeado: importa el .pem en" + info " chrome://settings/certificates (pestaña Autoridades)." + info " 3. Rapido y sucio (menos seguro): el navegador de 'web_proxy browser'" + info " ya usa --ignore-certificate-errors si no instalas el CA." +} + +# Genera e instala el unit systemd --user. El servicio corre mitmdump en +# foreground (systemd gestiona el proceso) con Restart=always. +cmd_install_service() { + local port out rotate enable_linger="no" + port="$(conf_get PORT "$DEFAULT_PORT")" + out="$(conf_get OUT "$DEFAULT_OUT")" + rotate="$(conf_get ROTATE "$DEFAULT_ROTATE")" + while [[ $# -gt 0 ]]; do + case "$1" in + --port) port="$2"; shift 2 ;; + --out) out="$2"; shift 2 ;; + --rotate-min) rotate="$2"; shift 2 ;; + --linger) enable_linger="yes"; shift ;; + *) err "install-service: flag desconocido: $1"; return 2 ;; + esac + done + + if [[ ! -x "$MITMDUMP_BIN" ]]; then + err "mitmdump no encontrado. Instala con: uv tool install mitmproxy" + return 1 + fi + + # Si hay un proxy manual corriendo, pararlo para no chocar de puerto. + if running_pid >/dev/null; then + info "Parando el proxy manual antes de instalar el servicio..." + cmd_stop + fi + + mkdir -p "$SYSTEMD_USER_DIR" "$out" + conf_write "$port" "$out" "$rotate" + + cat > "$SYSTEMD_USER_DIR/$SERVICE_NAME" </dev/null \ + && ok "Linger activado: el servicio sigue vivo aunque cierres sesion." \ + || err "No se pudo activar linger (requiere permisos). El servicio para al cerrar sesion." + fi + + sleep 1 + if service_active; then + ok "Servicio instalado y ACTIVO en 127.0.0.1:$port." + info " capturas -> $out (rotacion cada ${rotate} min)" + info " logs -> web_proxy logs" + info " navegador -> web_proxy browser" + [[ "$enable_linger" == "no" ]] && info " persistir tras logout -> web_proxy install-service --linger" + else + err "El servicio no arranco. Revisa: web_proxy logs" + return 1 + fi +} + +cmd_uninstall_service() { + systemctl --user disable --now "$SERVICE_NAME" 2>/dev/null + rm -f "$SYSTEMD_USER_DIR/$SERVICE_NAME" + systemctl --user daemon-reload + ok "Servicio $SERVICE_NAME desinstalado." +} + +cmd_logs() { + if service_active || systemctl --user cat "$SERVICE_NAME" &>/dev/null; then + journalctl --user -u "$SERVICE_NAME" -n "${1:-40}" --no-pager + else + local out + out="$(conf_get OUT "$DEFAULT_OUT")" + [[ -f "$out/mitmdump.log" ]] && tail -n "${1:-40}" "$out/mitmdump.log" \ + || info "No hay logs (servicio no instalado, sin log manual)." + fi +} + +usage() { + cat <<'EOF' +web_proxy — proxy de interceptacion HTTP/HTTPS liviano, siempre activo (mitmproxy) + +USO: web_proxy [opciones] + +Proxy: + start [--port N] [--out DIR] [--rotate-min N] Arranca el proxy (background, manual) + stop Para el proxy manual + restart [opciones de start] Reinicia el proxy manual + status Estado: proxy, servicio, capturas, CA + +Servicio (siempre activo, systemd --user): + install-service [--port N] [--out DIR] [--rotate-min N] [--linger] + Instala + arranca como servicio + stop-service / uninstall-service Para / desinstala el servicio + logs [N] Ultimas N lineas de log + +Navegacion: + browser [--url URL] [--port N] Lanza Chromium proxeado (perfil aislado) + ca Instrucciones para confiar en el CA (HTTPS) + +Consultar capturas: + query [FILTRO] [--all|--last] Vuelca flujos (default: ultima captura) + Filtros mitmproxy: "~m POST", "~c 500", "~d host" + har [SALIDA.har] [--all] Exporta a HAR + inspect [ARCHIVO.mitm] Abre UI web (mitmweb) sobre una captura + +Config: + WEB_PROXY_HOME directorio de estado (default ~/.web_proxy) + Defaults: puerto 8080, capturas ~/captures, rotacion 20 min + +Ejemplo completo: + web_proxy install-service --port 8080 --out ~/captures --rotate-min 20 + web_proxy ca # confiar en el CA para HTTPS + web_proxy browser # navega; todo queda capturado + web_proxy query "~m POST" # revisa los POST de la ultima ventana + web_proxy inspect # UI visual de la ultima captura +EOF +} + +# --- Dispatch ---------------------------------------------------------------- + +main() { + local cmd="${1:-help}" + shift || true + case "$cmd" in + start) cmd_start "$@" ;; + stop) cmd_stop "$@" ;; + restart) cmd_restart "$@" ;; + status) cmd_status "$@" ;; + browser) cmd_browser "$@" ;; + query) cmd_query "$@" ;; + har) cmd_har "$@" ;; + inspect) cmd_inspect "$@" ;; + ca) cmd_ca "$@" ;; + install-service) cmd_install_service "$@" ;; + stop-service) systemctl --user stop "$SERVICE_NAME" && ok "Servicio parado." ;; + uninstall-service) cmd_uninstall_service "$@" ;; + logs) cmd_logs "$@" ;; + help|-h|--help) usage ;; + *) err "Comando desconocido: $cmd"; echo; usage; return 2 ;; + esac +} + +main "$@"