tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1411Shell 141Markdown 84Rust 832898 Definitions

censar: la sonda de dominios dejó a gioser incomunicado — «sólo lee» era falso

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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x

5d1120e583bfac5520bcc4ac4ae044d84b0ffe13 · by Sergio · (1 month ago) · parent 95c16097e · browse the tree at this commit

Semantic delta · 3

scripts/mudanza/censar.py

1 files · +38 −4

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 = {