Auditoría Suprema · Monitoring sub-área 2 (protocolos/probes SNMP·ping·HTTP) — fixes s107
Contexto
Quinta tanda de la Auditoría Suprema (ver [[concept—onboarding—mapa-maestro-ecosistema-crearack]]) y segunda sub-área de monitoring (tras [[feature—monitoring—auditoria-sa1-s106]]). Cubre los probes / protocolos: los servicios que sondean la red desde el servidor — SNMP, ping ICMP, HTTP — más la correlación/agrupación de resultados (~1.3k LOC en 6 ficheros).
Resultado: 39 crudos → 37 dedup → 36 confirmados (4 ALTA / 17 MEDIA / 15 BAJA), 1 rechazado por verificación adversarial. 67 agentes, 3.73M tokens. Datos: auditoria-suprema/monitoring-sa2.{md,json}. PR #64→#65 (merge cd274098, CI verde, sin migraciones).
Decisión de Edu: “todo de una vez” (Regla 22, patrón [[feature—blueprints—auditoria-s105]] + sa1).
El hallazgo estrella: SSRF en los probes HTTP (ALTA)
HttpService.check / check_async / _check_ssl_cert conectaban a la URL recibida sin validar el destino. El endpoint /targets/{id}/http/check (operations.py) comprobaba is_private_ip(target.ip_address) pero después probaba config["http_url"] — un campo distinto, controlado por el usuario. Vector: target con ip_address público (pasa el guard) + http_url=http://169.254.169.254/latest/meta-data/ (metadata cloud), http://127.0.0.1:8428/ (VictoriaMetrics), o servicios internos por hostname Docker (http://cache:6379/, http://db:5432/). batch.py ni siquiera tenía el guard parcial. follow_redirects=True permitía además SSRF vía 302 a destino interno.
Modelo de amenaza confirmado: los probes server-side son para targets públicos — los dispositivos privados del cliente se monitorizan vía el Local Agent, no desde el servidor (de ahí que ping/HTTP/SNMP-poll ya saltaran is_private_ip). Por tanto un probe server-side hacia RFC1918/loopback/link-local es SSRF contra la propia infra, no monitoreo legítimo.
Fix — nuevo guard SSRF compartido monitoring/services/net_guard.py:
validate_destination_url(url)/validate_destination_host(host, port)→(is_safe, reason).- Rechaza esquema ≠ http/https, hostnames bloqueados (
localhost,metadata*), y resuelve el host (getaddrinfo) rechazando si CUALQUIER IP resuelta cae en loopback (127/8, ::1), link-local/metadata (169.254/16, fe80::/10), RFC1918 (10/8, 172.16/12, 192.168/16), CGNAT/overlay NetBird (100.64/10),0.0.0.0/8o ULA IPv6 (fc00::/7). - Aplicado en la capa de servicio (
check/check_async/_check_ssl_cert) → protege a todos los callers, incluido elbatch.py.
El proyecto ya tenía un guard equivalente para webhooks ITSM (notification_service.validate_webhook_url); net_guard lo generaliza para los probes (unificar ambos queda como carve-out de sub-área 4).
Resto de ALTA
- Endpoints SNMP sin gate —
snmp/test,snmp/interfaces,snmp/poll(monitoring/api/snmp.py) no aplicabanrequire_perm→ cualquier autenticado (incl.readonly) disparaba SNMP. Añadidorequire_perm(request, "observatory", "edit")a los 3. (Misma raíz Broken Access Control que sa1/blueprints; helper de s105.) - Escaneo SNMP de red interna —
snmp/testysnmp/interfacesno tenían el guardis_private_ipque sí tienesnmp/poll→ permitían sondear IPs internas arbitrarias. Añadido (mismo criterio: privados vía Agent). - Inyección PromQL en grupos —
group_service.query_group_metricinterpolabametricen el nombre de métrica (snmp_extras_<metric>) ytenant_id/target_idsen los selectores de la query VM sin sanear → lectura cross-tenant (misma clase que el fix de sa1 enmetrics_reader, en otro fichero). Validados contra allowlist_VALID_METRIC_NAME+ numéricos; entrada inválida →{"data": []}+ log.
Fixes correctness / fuga de info
snmp/poll200-mintiendo → devolvía{"status": "error"}(valor fuera de lasSTATUS_CHOICES) ante fallo SNMP. Ahora"down"(estado válido). (Heredado de sa1, verificado y cerrado aquí.)- DoS de memoria en HTTP probe → descargaba el body completo sin límite. Ahora lectura en streaming con cap de 2 MB + timing con
time.monotonic()+datetimeaware (sinutcnow()deprecado) + el campoerrorya no exponestr(exc)interno. except Exceptionmudos ensnmp_service.py(get_single/walk/get_interface_traffic) → ahoralogger.debug;test_connectionno devuelvestr(exc)al cliente.snmp_auth.py→ el log DEBUG ya no incluye elusernameSNMP v3.
Tests
15 nuevos en tests/api/test_monitoring.py: gate de permisos en los 3 probes SNMP (readonly→403, admin→skip private sin red), guard SSRF (net_guard + http_service.check bloquea metadata/loopback/RFC1918/esquema y permite IP pública), inyección PromQL en query_group_metric. 253 verde en tests/api/ (1 skip preexistente). Sin migraciones. ruff limpio.
Backlog Etapa 3 (carve-outs Regla 22)
- Probes síncronos dentro del request ASGI (SNMP walks ~30s, ping, HTTP) → async/Huey + polling (perf L, toca contrato frontend).
- Cifrado en reposo de credenciales SNMP v3 (
authKey/privKey/community en elconfigdict) → toca el modelo de credenciales (alinear conCredentialManager). Ya no se serializan al cliente (sa1) ni se loguean (sa2); falta el reposo. - IP-pinning anti-DNS-rebinding en
net_guard(hoy resuelve+bloquea, como el guard de webhooks; pinnear la IP validada en el transport httpx cierra el TOCTOU). - Defensa-en-profundidad en
ping_service(el caller ya guarda conis_private_ip). validate_webhook_url(ITSM, sub-área 4) podría delegar ennet_guard(evita drift de dos blocklists).correlation_service(subnet por troceo de string rompe IPv6/CIDR, N+1 enmatch_known_issue) ·group_service(ThreadPoolExecutor por request).
Relacionado
- [[feature—monitoring—auditoria-sa1-s106]] — sub-área 1, mismo dominio.
- [[feature—blueprints—auditoria-s105]] — dominio anterior, mismo patrón.
- Versión coloquial: [[concept—producto—observatory-mas-seguro]].
- Doc maestro:
auditoria-suprema/AUDITORIA_SUPREMA.md(repo workspace).
Véase también
- [[feature—monitoring—auditoria-sa1-s106]]
- [[concept—producto—observatory-mas-seguro]]