From 5d1120e583bfac5520bcc4ac4ae044d84b0ffe13 Mon Sep 17 00:00:00 2001 From: Sergio Date: Fri, 11 Sep 2026 14:53:05 +0000 Subject: [PATCH] =?UTF-8?q?censar:=20la=20sonda=20de=20dominios=20dej?= =?UTF-8?q?=C3=B3=20a=20gioser=20incomunicado=20=E2=80=94=20=C2=ABs=C3=B3l?= =?UTF-8?q?o=20lee=C2=BB=20era=20falso?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Censar gioser en `--local` disparó 29 peticiones HTTPS a sus propios dominios. Varios devuelven 502, y su `fail2ban` (jaula `caddy-backend-down`) **baneó la propia IP de la máquina**: gioser dejó de poder hablar con su propio gitea y los `git push` empezaron a fallar con «Could not read from remote repository». Un censo que deja la máquina incomunicada no es de sólo lectura. **La frase del script era la mentira**: «sólo lecturas, no se toca nada». No escribe nada, cierto — pero leer POR RED tiene efectos. Corregida. Tres arreglos: 1. **La sonda no se hace contra uno mismo.** Por defecto sólo con `--host`; en `--local` se saltea y se avisa por qué, con `--probe` para forzarlo. Además es metodológicamente mejor: lo que interesa es qué ve EL MUNDO, no qué ve la máquina de sí misma. 2. **Espaciada** medio segundo entre dominios, para no parecerle un escaneo al fail2ban del objetivo. 3. Clase nueva `sin-sondear`, para que un dominio sin datos no se confunda con un fósil. Desbaneado 204.168.193.248 de `caddy-backend-down`. Y queda la regla: **antes de sondear una máquina, pensar qué defensa propia le estás disparando.** Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x --- scripts/mudanza/censar.py | 42 +++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/scripts/mudanza/censar.py b/scripts/mudanza/censar.py index c988f29f..e5a9ebe5 100755 --- a/scripts/mudanza/censar.py +++ b/scripts/mudanza/censar.py @@ -1,6 +1,9 @@ #!/usr/bin/env python3 """censar.py — CENSO de una máquina: qué corre, qué sirve, qué datos tiene. Sin IA, sin adivinar. +⚠ «Sólo lee» hay que decirlo con cuidado: NO ESCRIBE nada, pero las sondas de RED dejan huella. Ver +`sondear_dominio` — censar gioser contra sí mismo hizo que su propio fail2ban lo baneara. + Primer paso de la mudanza (SDD 29). Produce un inventario TOML que un humano revisa y que las etapas siguientes consumen. NO toca nada: todo lo que hace son lecturas. @@ -159,7 +162,18 @@ def dominios(t): def sondear_dominio(d, ips_maquina): - """DNS + HTTP desde AFUERA (desde donde corre el censo). Determinista, sin juicio.""" + """DNS + HTTP desde donde corre el censo. Determinista, sin juicio. + + ⚠⚠ **NO se hace contra la máquina que uno está censando desde ella misma.** «Sólo lee» era falso: + leer POR RED tiene efectos. Medido el 2026-09-11 — censar gioser en modo `--local` disparó 29 + peticiones HTTPS a sus propios dominios, varios devuelven 502, y su `fail2ban` (jaula + `caddy-backend-down`) **baneó la propia IP de la máquina**. Resultado: gioser dejó de poder hablar + con su propio gitea y los `git push` empezaron a fallar con «Could not read from remote + repository». Un censo que deja la máquina incomunicada no es de sólo lectura. + + Y además es metodológicamente mejor: lo que interesa es **qué ve el mundo**, no qué ve la máquina + de sí misma. Por eso la sonda se hace desde donde corre el censo —el operador o la caja nueva— y + se SALTA cuando se censa en local, salvo `--probe` explícito.""" ip = "" try: r = subprocess.run(["getent", "ahostsv4", d], capture_output=True, text=True, timeout=10) @@ -388,7 +402,7 @@ def resumen(c): pd = {} for d in c["dominios"]: pd.setdefault(d["class"], []).append(d["domain"]) - for cl in ("vivo", "ya-mudado", "backend-caido", "fosil-sin-dns"): + for cl in ("vivo", "ya-mudado", "backend-caido", "fosil-sin-dns", "sin-sondear"): n = pd.get(cl, []) if n: print(f" {cl:16} {len(n):3} {' '.join(sorted(n)[:6])}{' …' if len(n) > 6 else ''}") @@ -406,6 +420,10 @@ def main(): ap.add_argument("--key", help="clave SSH") ap.add_argument("--local", action="store_true", help="censar esta máquina") ap.add_argument("--out", help="fichero TOML de salida (default: stdout sólo el resumen)") + ap.add_argument("--probe", action="store_true", + help="sondear los dominios por DNS+HTTP. Por defecto SÓLO con --host: hacerlo " + "contra uno mismo puede disparar el fail2ban de la propia máquina") + ap.add_argument("--no-probe", action="store_true", help="no sondear dominios") ap.add_argument("--data-root", action="append", default=[], help="raíz de datos a medir (repetible). Default: las habituales") a = ap.parse_args() @@ -413,14 +431,30 @@ def main(): ap.error("hace falta --host o --local") t = Objetivo(a.host, a.key) - print(" censando… (sólo lecturas, no se toca nada)", file=sys.stderr) + print(" censando… (no se ESCRIBE nada; las sondas de red sí dejan huella: ver --probe)", + file=sys.stderr) ident = identidad(t) vivos = procesos_vivos(t) decl = declarados(t) svc, huerfanos = clasificar(vivos, decl) pts = puertos(t) - doms = [sondear_dominio(d, set(ident["ips"])) for d in dominios(t)] + nombres = dominios(t) + sondear = (a.probe or bool(a.host)) and not a.no_probe + if nombres and not sondear: + print(f" ⚠ {len(nombres)} dominio(s) declarados, SIN sondear: censando en local, y pedirle", + file=sys.stderr) + print(" a la máquina sus propios dominios puede disparar su fail2ban y dejarla", + file=sys.stderr) + print(" incomunicada (pasó). Sondealos desde otra máquina, o forzá con --probe.", + file=sys.stderr) + doms = [{"domain": d, "dns": "", "http": "", "class": "sin-sondear"} for d in nombres] + else: + doms = [] + for i, d in enumerate(nombres): + if i: + time.sleep(0.5) # no parecer un escaneo ante el propio fail2ban del objetivo + doms.append(sondear_dominio(d, set(ident["ips"]))) raices = a.data_root or ["/var/www", "/var/lib", "/srv", "/opt", "/home", "/store"] dat = datos(t, raices) censo = {