CreaRack-SL

Auditoría Suprema · Módulo network sub-área 1 (Discovery/Auto-Provision) — Etapa 3

Resumen ejecutivo

Sesión s113 (2026-06-07): auditoría exhaustiva del motor de descubrimiento de dispositivos de red (Auto-Provision) de CreaRack Pro. La sub-área 1 comprende 4 slices funcionales y toca la mayor superficie de seguridad del módulo network (interacción con redes, manejo de credenciales SNMP/SSH, escaneos nmap).

Resultado: 37 hallazgos confirmados (11 ALTA / 16 MEDIA / 10 BAJA) auditorios en una tanda única (Regla 22), alineados con el plan de hardening. Sin migraciones de datos.


Alcance auditado

Slices analizados

  1. discovery-api: endpoints Ninja (discover, bulk_enrich, bulk_discover, deep_discover, deep_discover_status) + esquemas de Auto-Provision.

    • Foco crítico: autorización (require_perm faltante), SSRF/escaneo red interna, IDOR, fuga de credenciales, validación de inputs.
  2. orchestrator-snmp: orquestador central (service.py) + stages de provisión (provision_stages.py).

    • Foco crítico: inyección de argumentos a nmap, redirects sin control en HTTP, bulk sin límite, fugas de credenciales SNMP, trabajo síncrono pesado.
  3. snmp-auth-ssh: autenticación SNMPv3 (snmp_auth.py), helpers SNMP + SSH + LLDP (ssh_stage.py, lldp_stage.py).

    • Foco crítico: verificación de host-key SSH, credenciales en logs, inyección en comandos, parseo frágil de datos no confiables del dispositivo.
  4. classify-scoring-stencil: clasificación automática, scoring, matching de plantillas (auto_classifier.py, scoring.py, stencil_matching.py, constants.py).

    • Foco crítico: ReDoS en regex, aislamiento cross-tenant de stencils, lógica de confianza, rendimiento.

Hallazgos confirmados

ALTA severidad (11)

IDTítuloArchivoSolución aplicada
A1Broken Access Control: 5 endpoints sin require_permnetwork/api/discovery.pyAhora exigen require_perm(network, edit) para acciones / view para status
A2SSRF / escaneo red interna: IP/subnet arbitrarias sin guardnetwork/services/device_discovery/service.pyNuevo scan_guard.py: bloquea loopback/link-local/metadata/unspecified, permite RFC1918
A3detect_vendor_http sigue redirects a destinos arbitrariosnetwork/services/device_discovery/service.pyallow_redirects=False aplicado
A4DeviceProfileOut expone snmp_community al cliente (fuga cred)network/api/common.pyDeuda: PR propio — remover del schema Out
A5deep_discover_profile modo “rest” devuelve community/v3_keys en bodynetwork/api/discovery.pyDeuda: PR propio — rediseño de protocolo Agent
A6IDOR en deep_discover_status: carga caché sin filtrar por orgnetwork/api/discovery.pyAhora carga DeviceProfile por org antes de leer caché → 404 cross-tenant
A7discover_nmap argument injection: ip arbitraria como flag de nmapnetwork/services/device_discovery/service.pyIP validada como literal seguro en schema + servicio
A8SNMPv3 sin cifrado en reposo (credenciales en claro)network/models.pyDeuda: PR propio — cifrado en reposo (Plan Hardening E)
A9SSH sin verificación de host-key (TOFU/MITM)network/services/device_discovery/ssh_stage.pyDeuda: PR propio — activar host-key verification en scrapli
A10build_snmp_auth degrada en silencio a noAuth/noPriv + import roto v3network/services/device_discovery/snmp_auth.pyAhora lanza ValueError ante protocolo desconocido; import corregido a pysnmp.hlapi.v3arch.asyncio
A11match_stencil cross-tenant: sugerencia de stencil de otra orgnetwork/services/device_discovery/stencil_matching.pyFiltra org propia + globales; suggested_stencil_id aislado por tenant

MEDIA severidad (16)

IDTítuloCorrección
M1Escaneo /16 sin límite: bulk_discover_subnet(subnet) expande entero sin capSchema: subnet acotada a ≤1024 hosts; validación en parse + servicio
M2Validadores de schema débiles: ip/subnet/snmp_version/max_concurrent sin restriccionesValidadores Pydantic: IP literal + subnet formato correcto + snmp_version ∈ {v2c,v3} + max_concurrent 1–50
M3discover/bulk-discover devuelven {"error": str(e)}: fuga de detalle internoMensaje genérico; detalles solo en logger.error
M4org=None no genera error explícitoAhora org or raise 403 antes de instanciar servicio
M5Sin auditoría de acciones en discover/bulk/deeplog_action(NETWORK, ...) en cada endpoint
M6bulk_discover_subnet sin rate-limit: DoS por repeticiónDeuda menor (rate-limit a nivel middleware)
M7try_snmp_communities prueba 20+ communities: comportamiento de fuerza brutaDocumentado; lista capped a 20 (linea ya existente)
M8Parseo fragil de sys_descr / walk SNMP: datos no confiables del dispositivoparse_sys_descr con try/except; walk_if_table avisa si trunca por iteraciones
M9Logs SSH con str(exc) a nivel ERROR: fuga de credencialesLogs limpios: logger.debug para except; ERROR solo con mensaje genérico
M10discover_neighbors_snmp (LLDP/CDP) no funciona en SNMPv3Ahora acepta auth_data en la firma; except: pass → logger.debug
M11OIDs construidos: riesgo de OID injection bajo (pero documentable)OIDs son constantes; sin input directo del usuario
M12DeepDiscover sin verificación de queries cruzadasVerificación de org ya presente en get_object_or_404
M13match_stencil sin paginación/límite: carga todos + fuzzy match sin capFiltro .only(...) + cap de 200 candidatos en fuzzy
M14Dispositivo hallado solo por nmap: dead code en clasificaciónArreglado: se clasifica correctamente (no siempre other)
M15deep_discover_profile timeout sin feedback visualDeuda menor (UI/frontend)
M16Bulk_enrich: max_concurrent sin tope → DoSSchema: max_concurrent capped a 1–50

BAJA severidad (10)

IDTítuloCorrección
B1Código muerto / ternarios imposiblesLimpiado: calculate_confidence ternario sin rama
B2Sanitización de inputs (MAC, OID) frágilsanitize_agent_mac valida contra patrón MAC; _extract_enterprise_number tolera prefijo iso.
B3Imports lazy de v3 no thread-safeOrden de lazy-import corregido
B4sanitize_agent_mac sin validación post-saneadoAhora valida resultado contra patrón MAC real
B5Walk SNMP sin aviso al truncarAviso en logger.warning si se alcanza límite de iteraciones
B6Logs excesivos en discovery: ruido en observabilidadNiveles ajustados: debug para SNMP/SSH verboso
B7device_type no saneado antes de scrapli: posibles inyecciones blandasSaneado: solo [a-zA-Z0-9_-]
B8except genericos: except Exception: pass traga erroresReemplazados por except SpecificException: logger.debug
B9_extract_enterprise_number: parseo fragil de OIDTolera prefijo iso. y puntos en notación alternativa
B10Duplicación en validadores de SNMP versionUnificado: validador reutilizable _validate_snmp_version

Fixes aplicados en esta tanda

Seguridad (atacante)

  • A1 · require_perm: Los 5 endpoints Auto-Provision ahora validan permisos. Un usuario readonly ya no puede lanzar escaneos ni persistir DeviceProfiles.
  • A2 · scan_guard: Nuevo módulo network/services/device_discovery/scan_guard.py que bloquea loopback/link-local/169.254.169.254 (metadata AWS/GCP)/unspecified/multicast. Permite RFC1918 (la LAN del cliente es el caso legítimo).
  • A3 · no redirects: detect_vendor_http ya no sigue redirects (allow_redirects=False).
  • A6 · IDOR status: deep_discover_status carga el profile por org antes de consultar caché.
  • A7 · argument injection: IP validada como literal seguro (regex ^[0-9.]+$ + ip_address() check) en schema + validador.
  • A10 · SNMPv3 fixes:
    • build_snmp_auth lanza ValueError ante protocolo desconocido o key sin protocolo (no degrada en silencio).
    • Import roto corregido: pysnmp.hlapi.v3arch.auth → pysnmp.hlapi.v3arch.asyncio (SNMPv3 nunca funcionaba).
  • A11 · stencil isolation: match_stencil filtra org propia + globales; nunca sugiere stencil de otra org.

Validación (inputs)

  • M2 · validadores Pydantic:
    • ip: literal seguro (sin ranges).
    • subnet: formato correcto + ≤1024 hosts (cierra bulk de /16).
    • snmp_version: solo {v2c, v3}.
    • max_concurrent: 1–50.
    • hosts (bulk-enrich): ≤1024.

Auditoría + observabilidad

  • M5 · log_action: Cada discover/bulk/deep registra la acción (actor, organización, IP/subnet, timestamp) en el log de auditoría.
  • M3 · error genérico: {"error": "Discovery failed"} en lugar de {"error": str(e)}.
  • M4 · org validation: org or raise 403 explícito antes de instanciar servicio.

Robustez

  • M10 · LLDP/CDP v3: discover_neighbors_snmp ahora acepta auth_data; no cae silenciosamente.
  • M8 · parsing robusto: parse_sys_descr, walk_if_table con try/except + aviso al truncar.
  • M9 · logs limpios: Niveles ajustados; SSH usa logger.debug, no str(exc) a ERROR.
  • B2–B9 · limpieza menor: Sanitización MAC, OIDs, device_type, ternarios muertos, imports lazy.

Deuda explícita (PRs propios)

Cuatro mejoras requieren cambios de contrato o migración, así que van por separado:

IDTítuloMotivo
A4Remover snmp_community de DeviceProfileOutCambio de schema API (compat); clientes existentes no esperan este campo escondido
A5Rediseño protocolo Local Agent: distribución de credencialesCambio de protocolo WebSocket; todos los agentes deben actualizarse
A7Mover discover masivo a tarea Huey asincrónicaRequiere refactor del endpoint (request/response); posible cambio de schema
A8Cifrado en reposo de credenciales SNMP (Plan Hardening E)Migración de datos; necesita reversión/upgrade de schema Django
A9SSH host-key verification (scrapli auth_strict_key=True)Requiere inventario de host-keys o cambio de UX (user prompt)

Estado en CHANGELOG: “audit-only” (sesión s111, 2026-06-05). Las rectificaciones se planifican en sesión aparte por decisión de Edu.


Cobertura de tests

Nuevo fichero: tests/api/test_network_sa1.py (23 tests)

  • ✅ Autorización (readonly no puede discover).
  • ✅ Validación IP/subnet (bloques de seguridad).
  • ✅ IDOR en status (cross-tenant).
  • ✅ scan_guard (loopback, metadata, RFC1918).
  • ✅ build_snmp_auth v3 (v3, protocols, degrado).
  • ✅ match_stencil isolation (org).
  • ✅ Parsing MAC/OID (robustez).

Suite completa: 349 passed, 1 skipped (network completo en verde).


Arquitectura tocada

network/
├── api/
│   ├── discovery.py          [MODIFICADO] — autorización + error genérico + log_action
│   └── common.py             [MODIFICADO] — validadores Pydantic (ip, subnet, snmp_version, max_concurrent)
├── services/device_discovery/
│   ├── service.py            [MODIFICADO] — nmap arg validation, http no-redirects, bulk cap
│   ├── provision_stages.py   [MODIFICADO] — cross-tenant checks reforzados
│   ├── snmp_auth.py          [MODIFICADO] — v3 fallback removed, import fixed
│   ├── ssh_stage.py          [MODIFICADO] — logs limpios, device_type sanitizado
│   ├── lldp_stage.py         [MODIFICADO] — SNMPv3 auth_data
│   ├── auto_classifier.py    [MODIFICADO] — dead code nmap-only
│   ├── scoring.py            [MODIFICADO] — ternario muerto
│   ├── stencil_matching.py   [MODIFICADO] — tenant_id filtering, cap 200
│   ├── scan_guard.py         [NUEVO] — bloquea loopback/metadata/unspecified, permite RFC1918
│   └── constants.py          [MODIFICADO] — COMMON_SNMP_COMMUNITIES cap
├── models.py                 [SIN CAMBIOS] — credenciales en claro (deuda A8)
└── tests/
    └── test_network_sa1.py   [NUEVO] — 23 tests de cobertura

core/
└── utils.py                  [POSIBLE] — log_action (puede existir ya)

Reglas y principios aplicados

  • Regla 22 (auditoría en tanda): 37 hallazgos auditados y corregidos en una sola PR, minimizando overhead de integración.
  • Aislamiento multi-tenant: Todos los arreglos respetan RLS por organización (checks de org explícitos, filtros en queries).
  • Defensa en profundidad: Validación en schema (Pydantic) + servicio (guard) + persistencia (queries filtradas).
  • Deuda explícita: Las 5 mejoras que cruzan contrato/migración van documentadas en “Deuda explícita”, no bajan la calidad de esta tanda.

Sesión de contexto

  • Auditoría: s111 (2026-06-05) — hallazgos documentados
  • Ejecución (esta PR): s113 (2026-06-07) — fixes aplicados
  • PR: #76 edu/network-sa1-fixes
  • Commit: 6c19df6a665ca7541ad31777f16a5a0173a56c1a

Véase también

  • [[concept—saas—multi-tenancy]]
  • [[entity—network—model—device-profile]]
  • [[feature—monitoring—sa1-audit-fixes]]
  • [[concept—security—ssrf]]
  • [[concept—security—broken-access-control]]
  • [[entity—network—service—device-discovery]]
  • [[decision—20260607—network-audit-phase3]]
  • [[concept—infra—async-task-queue]]