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
This commit is contained in:
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user