censar: sonda DNS-only (--probe-dns) — y los dominios fósiles vuelven a la lista de decisiones
Censo real de gioser (204.168.193.248, identificado por IP y no por hostname, que dice «momento»): 19 vivos, 18 NO-DECLARADOS, 85 declarados-muertos, 29 dominios. **El HTTP toca al origen; el DNS no.** Sondear los dominios desde la propia máquina disparó su fail2ban y la dejó incomunicada, así que el censo en local los salteaba — y quedaban 29 de 66 ítems SIN recomendación por una precaución correcta aplicada de más. Lo que dispara la jaula es el HTTP: `getent` le pregunta al DNS, no al servidor. `--probe-dns` sondea sólo el nombre, sin UNA SOLA petición a gioser, y contesta la pregunta que más pesa en una mudanza: cuáles siguen apuntando acá y cuáles son fósiles. 29/29 clasificados: 12 apuntan acá, 4 ya se mudaron, 13 NO RESUELVEN. Lo que el DNS solo no puede decir es si el backend contesta, así que ésos quedan en `apunta-aca`, clase propia y NO `vivo`: decir «vivo» sin haber pedido una página sería la respuesta falsa con forma de respuesta que este censo existe para evitar. **Y un hueco de verdad: los `fosil-sin-dns` quedaban fuera de `entradas()`**, con el argumento de que un dominio sin DNS no necesita ningún paso. Cierto para el DNS, FALSO para lo que arrastra: son 13 de 29, y `terapeuta.ec` tenía 279 M de contenido y su bloque en el servidor web. Fuera de la lista eran invisibles, así que nadie decidía borrarlos y sus datos viajaban a la caja nueva por omisión — el «mudar fósiles» que este plan existe para evitar, y contra su propia regla 2 («lo que muere se dice por su nombre y con su tamaño, ANTES de borrar nada»). Ahora entran, con recomendación `muere` y el aviso de revisar qué dejaron en disco. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RomoxEGZUhaT4pob1QSX5x
This commit is contained in:
@@ -301,6 +301,39 @@ reconstruyen el servidor desde cero**:
|
||||
Ninguno de los tres es un log de lo que se hizo: los tres son entradas que se vuelven a ejecutar.
|
||||
Ésa es la diferencia entre mudar un servidor y poder mudarlo otra vez.
|
||||
|
||||
### 4.0 septies · El censo real de gioser, y dos huecos que destapó
|
||||
|
||||
Corrido contra `204.168.193.248` — que es `gioser.net`, identificado **por IP y no por hostname**,
|
||||
porque `hostname` dice `momento`:
|
||||
|
||||
```
|
||||
── servicios ── ── dominios (sonda DNS) ──
|
||||
vivo 19 apunta-aca 12
|
||||
⚠ no-declarado 18 ya-mudado 4
|
||||
declarado-muerto 85 fosil-sin-dns 13
|
||||
```
|
||||
|
||||
#### El HTTP toca al origen; el DNS no
|
||||
|
||||
Sondear los dominios desde la propia máquina disparó su `fail2ban` y la dejó incomunicada — por eso
|
||||
el censo en local los salteaba, y quedaban 29 de 66 ítems **sin recomendación**. Pero lo que dispara
|
||||
la jaula es el HTTP: `getent` le pregunta al DNS, no al servidor. Con `--probe-dns` se sondea sólo el
|
||||
nombre, **sin una sola petición a gioser**, y eso ya contesta la pregunta que más pesa: cuáles siguen
|
||||
apuntando acá y cuáles son fósiles. 29 de 29 clasificados; sólo **12 hay que reapuntar**.
|
||||
|
||||
Lo que el DNS solo no puede decir es si el backend contesta, así que esos quedan en `apunta-aca`
|
||||
— clase propia, **no `vivo`**. Decir «vivo» sin haber pedido una página sería la respuesta falsa con
|
||||
forma de respuesta que este censo existe para evitar.
|
||||
|
||||
#### Los fósiles quedaban fuera de la lista, y por eso sus datos viajaban
|
||||
|
||||
`entradas()` filtraba los `fosil-sin-dns` con el argumento de que un dominio sin DNS no necesita
|
||||
ningún paso. Es cierto para el DNS y **falso para lo que arrastra**: son 13 de 29, y `terapeuta.ec`
|
||||
tenía 279 M de contenido y su bloque en el servidor web. Fuera de la lista eran **invisibles**, así
|
||||
que nadie decidía borrarlos y sus datos viajaban a la caja nueva por omisión — exactamente el «mudar
|
||||
fósiles» que este plan existe para evitar, y contra la regla 2, que dice que lo que muere se nombra
|
||||
**antes** de borrar nada.
|
||||
|
||||
### 4.0 sexies · El centro ENGANCHADO al plan
|
||||
|
||||
El plan ya decía «cambiar de servidor web obliga a REESCRIBIR la configuración entera». Es cierto y
|
||||
|
||||
@@ -222,8 +222,8 @@ def dominios(t):
|
||||
return sorted(nombres)
|
||||
|
||||
|
||||
def sondear_dominio(d, ips_maquina):
|
||||
"""DNS + HTTP desde donde corre el censo. Determinista, sin juicio.
|
||||
def sondear_dominio(d, ips_maquina, http=True):
|
||||
"""DNS (+ HTTP opcional) 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
|
||||
@@ -234,7 +234,15 @@ def sondear_dominio(d, ips_maquina):
|
||||
|
||||
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."""
|
||||
se SALTA cuando se censa en local, salvo `--probe` explícito.
|
||||
|
||||
── PERO EL DNS NO TOCA AL ORIGEN ───────────────────────────────────────────────────────────
|
||||
Lo que dispara el fail2ban es el HTTP, no la resolución: `getent` le pregunta al DNS, no al
|
||||
servidor. Con `--probe-dns` se sondea SÓLO el nombre, y eso ya contesta la pregunta que más
|
||||
pesa en una mudanza — **cuáles de estos dominios siguen apuntando acá y cuáles son fósiles**.
|
||||
Lo que el DNS solo NO puede decir es si el backend contesta, así que esos dominios quedan en
|
||||
`apunta-aca`, que es una clase propia y no `vivo`: decir «vivo» sin haber pedido una página
|
||||
sería exactamente la respuesta falsa con forma de respuesta que este censo existe para evitar."""
|
||||
ip = ""
|
||||
try:
|
||||
r = subprocess.run(["getent", "ahostsv4", d], capture_output=True, text=True, timeout=10)
|
||||
@@ -242,8 +250,8 @@ def sondear_dominio(d, ips_maquina):
|
||||
ip = r.stdout.split()[0]
|
||||
except Exception:
|
||||
pass
|
||||
code = "000"
|
||||
if ip:
|
||||
code = ""
|
||||
if ip and http:
|
||||
try:
|
||||
r = subprocess.run(["curl", "-sk", "-o", "/dev/null", "-w", "%{http_code}",
|
||||
"--max-time", "8", f"https://{d}/"], capture_output=True, text=True, timeout=20)
|
||||
@@ -254,6 +262,8 @@ def sondear_dominio(d, ips_maquina):
|
||||
clase = "fosil-sin-dns"
|
||||
elif ip not in ips_maquina:
|
||||
clase = "ya-mudado" # resuelve a otra máquina: el bloque de acá es fósil
|
||||
elif not http:
|
||||
clase = "apunta-aca" # resuelve acá; si SIRVE no se preguntó, y no se inventa
|
||||
elif code.startswith(("2", "3")):
|
||||
clase = "vivo"
|
||||
else:
|
||||
@@ -467,7 +477,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", "sin-sondear"):
|
||||
for cl in ("vivo", "apunta-aca", "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 ''}")
|
||||
@@ -488,6 +498,9 @@ def main():
|
||||
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("--probe-dns", action="store_true",
|
||||
help="sondear SÓLO el DNS (sin HTTP): no toca al origen, así que es seguro "
|
||||
"contra uno mismo. Dice qué dominios son fósiles y cuáles apuntan acá")
|
||||
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")
|
||||
@@ -516,7 +529,8 @@ def main():
|
||||
e['paquete'] = dn.get(exe, '')
|
||||
pts = puertos(t)
|
||||
nombres = dominios(t)
|
||||
sondear = (a.probe or bool(a.host)) and not a.no_probe
|
||||
sondear = (a.probe or a.probe_dns or bool(a.host)) and not a.no_probe
|
||||
http_ok = not a.probe_dns
|
||||
if nombres and not sondear:
|
||||
print(f" ⚠ {len(nombres)} dominio(s) declarados, SIN sondear: censando en local, y pedirle",
|
||||
file=sys.stderr)
|
||||
@@ -528,9 +542,9 @@ def main():
|
||||
else:
|
||||
doms = []
|
||||
for i, d in enumerate(nombres):
|
||||
if i:
|
||||
if i and http_ok:
|
||||
time.sleep(0.5) # no parecer un escaneo ante el propio fail2ban del objetivo
|
||||
doms.append(sondear_dominio(d, set(ident["ips"])))
|
||||
doms.append(sondear_dominio(d, set(ident["ips"]), http_ok))
|
||||
raices = a.data_root or ["/var/www", "/var/lib", "/srv", "/opt", "/home", "/store"]
|
||||
dat = datos(t, raices)
|
||||
censo = {
|
||||
|
||||
@@ -76,8 +76,13 @@ def entradas(censo):
|
||||
if s.get("class") != "declarado-muerto":
|
||||
out.append(("servicio", s["name"], s))
|
||||
for d in censo.get("dominio", []):
|
||||
if d.get("class") != "fosil-sin-dns":
|
||||
out.append(("dominio", d["domain"], d))
|
||||
# ⚠ Los `fosil-sin-dns` ENTRAN. Antes se filtraban con el argumento de que un dominio sin DNS
|
||||
# no necesita ningún paso — y es cierto para el DNS, pero no para lo que arrastra: en gioser
|
||||
# son 13 de 29, y `terapeuta.ec` tenía 279 M de contenido y su bloque en el caddy. Sacarlos
|
||||
# de la lista los volvía INVISIBLES, así que nadie decidía borrarlos y sus datos viajaban a
|
||||
# la caja nueva por omisión. Es exactamente el «mudar fósiles» que este plan existe para
|
||||
# evitar, y la regla 2 dice que lo que muere se nombra ANTES de borrar nada.
|
||||
out.append(("dominio", d["domain"], d))
|
||||
for d in censo.get("datos", []):
|
||||
out.append(("datos", d["path"], d))
|
||||
return out
|
||||
@@ -333,6 +338,17 @@ def recomendar(tipo, e, datos_que_mueren=()):
|
||||
return "muere", f"su DNS ya apunta a {e.get('dns','otra máquina')}: el bloque de acá es fósil"
|
||||
if c == "backend-caido":
|
||||
return "muere", f"resuelve acá pero devuelve {e.get('http','5xx')}: no hay backend"
|
||||
if c == "apunta-aca":
|
||||
# El DNS dice que apunta acá; si SIRVE no se preguntó. Recomendar «muda» sería tratar el
|
||||
# silencio como una respuesta afirmativa, que es el error que este censo evita en todos
|
||||
# los demás lugares. Se recomienda mudar porque el DNS hay que reapuntarlo igual, pero
|
||||
# se DICE lo que no se miró.
|
||||
return "muda", (f"su DNS apunta acá ({e.get('dns','?')}), así que hay que reapuntarlo. "
|
||||
"⚠ NO se sondeó por HTTP: si el backend estaba caído, esto muda un fósil")
|
||||
if c == "fosil-sin-dns":
|
||||
return "muere", ("no resuelve en ningún DNS: nadie puede llegar a él. Revisá si dejó "
|
||||
"contenido en disco o un bloque en el servidor web — eso sí viaja si "
|
||||
"nadie lo borra")
|
||||
if c == "sin-sondear":
|
||||
return "", "sin sondear (censado en local): hace falta mirarlo desde afuera"
|
||||
return "", ""
|
||||
|
||||
Reference in New Issue
Block a user