CreaRack-SL

Liquidación de las rondas 30-08/06-09 · Cuarto PR: el descubrimiento de red deja de mentir (network)

Cuándo

06-09-2026 · commit e44d987187044c1e785232c61ea544fcc208d990 (PR #513, v1.120.0). Cuarto PR de una serie que liquida hallazgos pendientes de las rondas de auditoría del 30-08 y del 06-09 (serie distinta de la cola por dominio de la task #286: los PRs anteriores de esta misma serie cerraron las tasks #292, #279, #277/#293/#294/#296.1). Este cierra #291 y #278 (ALTA), #280 (ALTA), el punto 4 de #281 y los puntos 5 y 6 de #282, todos en el módulo de descubrimiento de red (network/Auto-Provision). 94 tests backend + 90 de frontend en verde.

Síntomas visibles

  1. #291 — Un host que fallaba el enriquecimiento (enrich_one en network/api/discovery.py) se guardaba igualmente como ficha con discovery_method="ping" y confianza 10-20, sin last_verified — indistinguible en pantalla de un hallazgo real por ping. En PROD había 114 fichas así, creadas en dos ráfagas de 57 el 21-08.
  2. #278 — Con el Agente local sin WebSocket, “Deep Discover” mostraba aviso verde de éxito en unos 2 segundos sin haber hecho ningún sondeo real. 201 de 261 fichas en PROD viven hoy en ese estado no verificado.
  3. #280 — El editor de ficha de dispositivo (device_card_editor.js) tenía una lista TYPE_OPTS con 7 valores frente a los 11 que acepta el modelo Device. Al abrir y guardar la ficha de un tipo no listado (p. ej. media_player), el desplegable quedaba en su primera opción y el guardado la persistía — convirtiendo la ficha en “router” de forma permanente. 105 fichas de cartelería en riesgo.
  4. #281 pt.4 — El enlace automático ficha-dispositivo (matching por IP) nunca encontraba coincidencias, aunque el hallazgo original apuntaba a una causa distinta (lectura de IP sin descifrar). Medido en PROD: 6 dispositivos con management_config, los 6 con IP descifrable, 0 con la IP en texto claro.
  5. #282 pt.5/pt.6 — La chapa de “Deep discovery completed” siempre citaba “0 MIBs loaded” (dato nunca poblado). Y los valores discovery_method="http" / device_type="ups" se escribían en producción sin estar declarados en las choices del modelo, con ups aceptado en un endpoint (assign.py) y rechazado con 400 en otro (profiles.py).

Causa raíz

  • #291: el bloque except de enrich_one no distinguía “no pude verificar este host” de “lo verifiqué y es un ping válido” — ambos casos producían el mismo tipo de ficha.
  • #278: el modo rest (Agente sin WebSocket) devuelve tareas para que el navegador las ejecute y solo señala fallo real vía resp.error; el consumidor del asistente de discovery no distinguía “tareas entregadas y agotadas sin respuesta” de “sondeo completado con éxito”. Otros 4 consumidores del mismo patrón sí lo hacían bien — la lógica correcta existía pero no estaba compartida (violación de la Regla 2, no duplicar código).
  • #280: dos listas independientes describiendo los mismos tipos de dispositivo — TYPE_OPTS en el frontend y DEVICE_TYPE_CHOICES en el modelo Django — se fueron desincronizando con cada tipo nuevo añadido al modelo sin propagar al frontend.
  • #281.4: el hallazgo original diagnosticó mal el síntoma. El código ya llamaba a Device.management_ip, que sí descifra. El defecto real vivía un nivel más abajo: el prefiltro SQL management_config__contains=<ip> buscaba la IP en claro dentro de un campo cuyo valor está cifrado con Fernet — cifrado no determinista, así que un LIKE/contains no puede casar nunca un ciphertext contra un texto plano, sin importar si la IP es correcta. El test que cubría este camino ocultaba el bug porque creaba los management_config de prueba sin cifrar.
  • #282.6: dos endpoints (assign.py, profiles.py) validaban tipos de dispositivo contra listas propias en vez de una fuente única (ASSIGNABLE_TYPES duplicaba DEVICE_TYPE_CHOICES con desfase).

Fix aplicado

Commit e44d987187044c1e785232c61ea544fcc208d990 (PR #513):

  • #291: enrich_one ya no fabrica una ficha “ping” ante una excepción — el host fallido vuelve en una lista failed (con el tipo de excepción) que la pantalla de resultados muestra aparte. Las 114 fichas ya creadas en PROD se dejan tal cual — decisión de Edu, un re-escaneo las corrige. De paso cierra el diferido network-#12.
  • #278: nuevo módulo compartido static/js/utils/deepDiscoverAgent.js; los 5 consumidores (incluidos wireless, ups, signage, monitoring) pasan a usarlo. Agotar los reintentos del sondeo del Agente ya no se interpreta como éxito.
  • #280: nuevo endpoint device-type-choices que sirve las opciones directamente desde DEVICE_TYPE_CHOICES del modelo; device_card_editor.js consulta ese endpoint en vez de mantener TYPE_OPTS como lista paralela.
  • #281.4: se elimina el prefiltro SQL ciego; el matching por IP se hace en Python descifrando management_config de los dispositivos no borrados de la organización (en PROD acota de 423 dispositivos a 6, coste asumible). El test que ocultaba el bug se corrigió para cifrar los management_config de prueba igual que en producción.
  • #282.5: se retira del tooltip la cifra de “N MIBs loaded” que nunca se poblaba.
  • #282.6: migración 0064_add_ups_http_choices declara http y ups en las choices del modelo; se borra ASSIGNABLE_TYPES para que assign.py y profiles.py validen contra la misma fuente (DEVICE_TYPE_CHOICES).
  • Queda abierto — punto 7 de #282 (modal de grupos del Terminal): existe DeviceManager.js::_applyCommonGroupChecks, un mecanismo que sí intenta premarcar grupos por intersección, y que la descripción del hallazgo original no mencionaba. Sin trazar si el dato le llega poblado, no se pudo confirmar si el bug reproduce tal cual — se deja para revisarlo aparte en vez de aplicar un parche a ciegas.

Lecciones

  • Un except que “guarda algo de todos modos” para no perder el intento fabrica un dato indistinguible de uno legítimo. Cuando falla la verificación, el resultado debe volver marcado como fallo, nunca como un hallazgo de baja confianza silencioso.
  • Cifrado no determinista (Fernet) y filtrado SQL en claro son incompatibles: un prefiltro contains/LIKE sobre un campo cifrado siempre devuelve vacío, nunca falla ruidosamente — el bug pasa desapercibido porque “no encuentra nada” parece un resultado plausible.
  • Un test que prepara sus fixtures sin replicar el cifrado real de producción puede dar cobertura falsa: pasaba en verde tapando exactamente el bug que debía atrapar.
  • Cuando el diagnóstico de un hallazgo de auditoría resulta incompleto o erróneo, el PR de cierre debe decirlo explícitamente (aquí, #281.4) en vez de aplicar el fix que el hallazgo sugería sin comprobar la causa real.
  • Dos fuentes de verdad describiendo el mismo catálogo (choices de modelo vs. lista de frontend, o ASSIGNABLE_TYPES vs. DEVICE_TYPE_CHOICES) se desincronizan con el tiempo; la corrección estructural es servir la lista desde una única fuente, no parchear la copia.

Preventivos futuros

  • Punto 7 de #282 (modal de grupos del Terminal) queda pendiente de trazar antes de tocarlo.
  • Las 114 fichas discovery_method="ping" ya creadas en PROD antes de este fix no se han corregido retroactivamente; quedan a la espera de un re-escaneo natural.

Véase también

  • [[feature—network—discovery-audit-sa1]]
  • [[entity—network—service—device-config]]
  • [[incident—20260904—auditoria-suprema-2-cola-network-agente-offline-y-whitelist-vendor]]
  • [[incident—20260904—auditoria-suprema-2-cola-monitoring-d-deep-discovery-wireless-ups]]