Files
web_proxy/web_proxy
T
egutierrez 6e094dce97 chore: auto-commit (1 archivos)
- web_proxy

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 22:25:59 +02:00

584 lines
22 KiB
Bash
Executable File

#!/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"
EXT_DIR="$SCRIPT_DIR/extension"
# --- 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
DEFAULT_WEB_PORT=8081
DEFAULT_MAX_MB=2048 # tope de tamaño del directorio de capturas (MB); 0 = sin limite
DEFAULT_MAX_DAYS=7 # antiguedad maxima de una captura (dias); 0 = sin limite
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" web_port="${4:-}" web_pass="${5:-}" max_mb="${6:-}" max_days="${7:-}"
cat > "$CONFFILE" <<EOF
PORT=$port
OUT=$out
ROTATE=$rotate
WEB_PORT=$web_port
WEB_PASS=$web_pass
MAX_MB=$max_mb
MAX_DAYS=$max_days
EOF
}
# URL base de la UI de registros en vivo (sin token; la auth es por password).
web_ui_url() {
local web_port
web_port="$(conf_get WEB_PORT "")"
[[ -z "$web_port" ]] && return 1
printf 'http://127.0.0.1:%s/' "$web_port"
}
# PID del proxy manual, si vive. Imprime el PID o nada.
running_pid() {
[[ -f "$PIDFILE" ]] || return 1
local pid
pid="$(cat "$PIDFILE" 2>/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")"
local max_mb max_days
max_mb="$(conf_get MAX_MB "$DEFAULT_MAX_MB")"
max_days="$(conf_get MAX_DAYS "$DEFAULT_MAX_DAYS")"
info "web_proxy — estado"
info " home: $WEB_PROXY_HOME"
info " capturas: $out"
info " rotacion: cada ${rotate} min"
info " retencion: max ${max_mb} MB, max ${max_days} dias"
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
local web_port web_pass
web_port="$(conf_get WEB_PORT "")"
web_pass="$(conf_get WEB_PASS "")"
if [[ -n "$web_port" ]]; then
ok " UI viva: http://127.0.0.1:$web_port (registros en tiempo real)"
[[ -n "$web_pass" ]] && info " UI login: password $web_pass (deja el usuario vacio)"
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 show_ui="yes" web_port mode="ext"
proxy_port="$(conf_get PORT "$DEFAULT_PORT")"
web_port="$(conf_get WEB_PORT "")"
while [[ $# -gt 0 ]]; do
case "$1" in
--url) url="$2"; shift 2 ;;
--port) proxy_port="$2"; shift 2 ;;
--no-ui) show_ui="no"; shift ;;
--fixed) mode="fixed"; shift ;; # proxy siempre activo, sin extension
--ext) mode="ext"; shift ;; # extension con toggle (default)
*) 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=(--profile "$WEB_PROXY_HOME/chromium-profile")
if [[ "$mode" == "fixed" ]]; then
# Proxy fijo: captura desde el primer request, sin toggle.
args+=(--proxy "http://127.0.0.1:${proxy_port}")
else
# Modo extension: la extension web_proxy toggle controla el proxy. Se
# carga desempaquetada y el navegador arranca SIN proxy fijo (toggle
# OFF por defecto). Un clic en su icono activa la captura.
args+=(--proxy none --ext "$EXT_DIR")
info "Extension de captura cargada. Clic en su icono (barra de extensiones) para ACTIVAR el proxy."
info "El proxy activo por defecto es 127.0.0.1:${proxy_port} (Captura web_proxy)."
fi
# Primera pestaña: la UI de registros en vivo (si el servicio corre en modo
# web). La UI es loopback y no se proxea, asi que carga directa. Las pestañas
# a sitios reales pasan por el proxy y aparecen en la UI en tiempo real.
local ui_url=""
[[ "$show_ui" == "yes" && -n "$web_port" ]] && ui_url="http://127.0.0.1:${web_port}"
if [[ -n "$ui_url" ]]; then
args+=(--url "$ui_url")
[[ -n "$url" ]] && args+=(--extra "$url")
local web_pass
web_pass="$(conf_get WEB_PASS "")"
[[ -n "$web_pass" ]] && info "UI de registros: login con password '$web_pass' (usuario vacio). Se recuerda en este perfil."
elif [[ -n "$url" ]]; then
args+=(--url "$url")
fi
bash "$BROWSER_FN" "${args[@]}"
}
# Abre solo la UI de registros en vivo en el navegador por defecto del sistema.
cmd_ui() {
local web_port
web_port="$(conf_get WEB_PORT "")"
if [[ -z "$web_port" ]]; then
err "El servicio no corre en modo web. Reinstala con: web_proxy install-service --web"
return 1
fi
local ui_url="http://127.0.0.1:${web_port}"
local web_pass
web_pass="$(conf_get WEB_PASS "")"
info "UI de registros en vivo: $ui_url"
[[ -n "$web_pass" ]] && info "login con password '$web_pass' (usuario vacio)"
if command -v xdg-open &>/dev/null; then
xdg-open "$ui_url" >/dev/null 2>&1 &
fi
}
# 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 en foreground
# (systemd gestiona el proceso) con Restart=always. Con --web usa mitmweb, que
# expone una UI web en vivo (estilo Burp/ZAP) ademas de capturar a disco; sin
# --web usa mitmdump headless.
cmd_install_service() {
local port out rotate enable_linger="no" web="no" web_port web_pass max_mb max_days
port="$(conf_get PORT "$DEFAULT_PORT")"
out="$(conf_get OUT "$DEFAULT_OUT")"
rotate="$(conf_get ROTATE "$DEFAULT_ROTATE")"
web_port="$(conf_get WEB_PORT "$DEFAULT_WEB_PORT")"
web_pass="$(conf_get WEB_PASS "")"
max_mb="$(conf_get MAX_MB "$DEFAULT_MAX_MB")"
max_days="$(conf_get MAX_DAYS "$DEFAULT_MAX_DAYS")"
[[ -n "$(conf_get WEB_PORT "")" ]] && web="yes"
while [[ $# -gt 0 ]]; do
case "$1" in
--port) port="$2"; shift 2 ;;
--out) out="$2"; shift 2 ;;
--rotate-min) rotate="$2"; shift 2 ;;
--max-mb) max_mb="$2"; shift 2 ;;
--max-days) max_days="$2"; shift 2 ;;
--web) web="yes"; shift ;;
--web-port) web="yes"; web_port="$2"; shift 2 ;;
--web-password) web="yes"; web_pass="$2"; shift 2 ;;
--headless) web="no"; shift ;;
--linger) enable_linger="yes"; shift ;;
*) err "install-service: flag desconocido: $1"; return 2 ;;
esac
done
[[ "$web" == "yes" && -z "$web_port" ]] && web_port="$DEFAULT_WEB_PORT"
# Password estable para la UI (la auth por token de mitmweb cambia en cada
# arranque; un password fijo da URL estable y cookie persistente en el perfil
# del navegador). Se genera uno aleatorio la primera vez.
if [[ "$web" == "yes" && -z "$web_pass" ]]; then
web_pass="$(tr -dc 'a-z0-9' </dev/urandom 2>/dev/null | head -c 10)"
[[ -z "$web_pass" ]] && web_pass="webproxy"
fi
local retention="--set max_total_mb=$max_mb --set max_age_days=$max_days"
local bin="$MITMDUMP_BIN" exec_line
if [[ "$web" == "yes" ]]; then
bin="$MITMWEB_BIN"
exec_line="$MITMWEB_BIN --no-web-open-browser --web-host 127.0.0.1 --web-port $web_port --set web_password=$web_pass -s $ADDON_PATH --set rotate_min=$rotate --set capture_dir=$out --set exclude_hosts=127.0.0.1:$web_port,localhost:$web_port $retention --listen-port $port"
else
exec_line="$MITMDUMP_BIN -s $ADDON_PATH --set rotate_min=$rotate --set capture_dir=$out $retention --listen-port $port"
fi
if [[ ! -x "$bin" ]]; then
err "$(basename "$bin") 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" \
"$([[ "$web" == "yes" ]] && echo "$web_port")" \
"$([[ "$web" == "yes" ]] && echo "$web_pass")" \
"$max_mb" "$max_days"
cat > "$SYSTEMD_USER_DIR/$SERVICE_NAME" <<EOF
[Unit]
Description=web_proxy — proxy de interceptacion HTTP/HTTPS liviano (mitmproxy)
After=network.target
[Service]
Type=simple
ExecStart=$exec_line
Restart=always
RestartSec=2
# Restart=always (no on-failure): un SIGTERM limpio es exit success y
# on-failure NO reiniciaria, dejando el proxy muerto en silencio.
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable "$SERVICE_NAME"
# restart (no enable --now): si el servicio ya estaba activo, --now NO lo
# reinicia y seguiria corriendo con el unit viejo (password/puerto previos).
# restart fuerza recarga del unit actual, arrancando si estaba parado.
systemctl --user restart "$SERVICE_NAME"
if [[ "$enable_linger" == "yes" ]]; then
loginctl enable-linger "$USER" 2>/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 " retencion -> max ${max_mb} MB, max ${max_days} dias (borra las mas viejas al rotar)"
if [[ "$web" == "yes" ]]; then
ok " UI viva -> http://127.0.0.1:$web_port (registros en tiempo real)"
info " UI login -> deja el usuario vacio, password: $web_pass"
fi
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 <comando> [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] [--web] [--web-port N] [--linger]
Instala + arranca como servicio
--web: UI de registros en vivo (mitmweb, estilo Burp)
stop-service / uninstall-service Para / desinstala el servicio
logs [N] Ultimas N lineas de log
Navegacion:
browser [--url URL] [--port N] [--no-ui] Lanza Chromium proxeado (perfil aislado).
Abre la UI de registros en vivo como primera pestaña.
ui Abre solo la UI de registros en el navegador del sistema
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 "$@" ;;
ui) cmd_ui "$@" ;;
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 "$@"