Auditoría Suprema · Monitoring sub-área 1 (targets+métricas+ingesta) — fixes s106
Contexto
Cuarto dominio de la Auditoría Suprema (ver [[concept—onboarding—mapa-maestro-ecosistema-crearack]]). monitoring es el más grande del producto (16.7k LOC) → troceado en 6 sub-áreas, 1 por sesión. Esta página documenta la sub-área 1 (targets + métricas + ingesta, 2.821 LOC) auditada y arreglada en s106 (PR #64, mergeado, CI verde).
Resultado de la auditoría: 47 hallazgos crudos → 44 dedup → 36 confirmados (6 ALTA / 12 MEDIA / 18 BAJA), 8 rechazados por verificación adversarial. Datos: auditoria-suprema/monitoring-sa1.{md,json}.
Decisión de Edu: “todo de una vez” (Regla 22, patrón [[feature—blueprints—auditoria-s105]]).
Patrón raíz: Broken Access Control sistémico
grep require_perm|has_permission|is_admin en TODA la app monitoring = 0 ocurrencias antes de s106. Ni lecturas, ni mutaciones, ni acciones de red estaban gateadas; el único portero era la autenticación global (config/urls.py:_global_api_auth) + el aislamiento por org de get_target_or_404. Un usuario readonly (rol DEFAULT, observatory:view) podía crear/editar/borrar targets y disparar polling activo.
Fixes de seguridad (ALTA)
- Gate de permisos —
core.utils.require_perm(request, "observatory", view/edit)entargets.py,operations.py,batch.py,metrics.py. Lecturas →view; mutaciones y acciones de red (ping/SNMP/HTTP/deep-discover) →edit. Mismo helper reutilizable creado en s105. - Credenciales SNMP fuera de la respuesta —
MonitoringTargetOutserializaba elconfigcompleto (consnmp_community,snmp_v3_auth_key,snmp_v3_priv_key). Ahoraresolve_configdevuelveMonitoringTarget.public_config(strip por substring:community|password|auth_key|priv_key|secret|credential) + flagsnmp_configured. El PUT (update_target) hace merge defensivo: re-inyecta los secretos existentes que el cliente no reenvía (porque GET no los expone), evitando que un round-trip los borre. - Inyección PromQL —
metrics.pyconstruía elmetric_nameVM concatenando claves deconfig['monitoring_oids']/['fast_poll_oids'](controladas por el usuario) e interpolaba sin sanear enmetrics_reader.get_generic_metric(f'last_over_time({metric_name}{{tenant_id="..."}}...')). Como el único aislamiento multi-tenant en VictoriaMetrics es el labeltenant_iddentro de la query, una clave con}rompía el selector → lectura cross-tenant. Ahora valida contra allowlist_VALID_METRIC_NAME = ^[a-zA-Z_:][a-zA-Z0-9_:]*$; claves inválidas se descartan + log. - Guard
org=None—require_org()encommon.pylanzaHttpError(403)en lugar del500/AttributeError(que filtrabastr(exc)por el handler global) cuando el principal no tiene organización (heatmap/health/CRUD bajo Agent JWT).
Bug nuevo descubierto al verificar: routing batch 405
batch_router se registraba después de targets_router en monitoring/api/__init__.py. Como Ninja usa segmentos de path string, /targets/batch-ping|batch-snmp|batch-http quedaban ensombrecidas por /targets/{target_id} y devolvían 405 Method Not Allowed (POST inexistente en esa ruta) → los endpoints batch eran inalcanzables. Reordenado batch_router antes de targets_router. El gate añadido a batch ahora es efectivo.
Lección: las rutas literales de un router deben registrarse antes que las paramétricas de otro router montado en el mismo prefijo.
Fixes correctness / limpieza
uptime_pct(get_target_stats): denominador corregido a muestraspacket_loss(eralatency→ sesgo, incluso >100%).ping_targets.py:filter(monitor_type="ping")referenciaba un campo eliminado en migración 0003 (ahora@property) →FieldError. Cambiado aping_enabled=True. (No estaba en ningún cron — dead code legacy, pero corregido.)- Helper
notify_agents()reemplaza 6×except Exception: passmudos (ahora loguean). - Helper
_merge_vendor_oids()deduplica el bloque VendorProfile de/vm/extrasy/vm/all(+ elimina unexcept: passsilencioso). reachableunificado (status != "down") en ramas de IP privada/ping real.str(exc)ya no se devuelve al cliente en batch/operations (mensaje genérico + log).bulk_createde los MetricSample en batch-snmp ·.exists()redundante fuera enaggregate_metrics· clamp de rangoshours/days+ slice en/bandwidth· validación de input enMonitoringTargetIn(ip/scope/interval/timeout).
Tests
16 nuevos en tests/api/test_monitoring.py: autorización (readonly→403 en crear/borrar/batch), no-fuga de credenciales SNMP en GET, merge defensivo en PUT, aislamiento batch cross-org, guard de inyección PromQL. 238 verde en tests/api/ (1 skip preexistente). Sin migraciones (solo properties/methods). ruff check + format limpios.
Backlog Etapa 3 (carve-outs Regla 22 / fuera de sub-área)
- RLS + FK
organizationenMetricSample/AggregatedMetric: hoy solo aislados por FKtargeta nivel ORM (no hay fuga explotable, pero falta la capa RLS). Migración sobre tabla de volumen que Dokploy auto-aplica → PR propio. - Paso a async/Huey de los polls síncronos en el request (perf L, toca contrato frontend).
- Audit log de mutaciones (no hay patrón genérico).
- Cambio de defaults del modelo (
http_enabled/community) — toca Auto-Provision. - Soft-delete/retención/CASCADE (arquitectónico).
- Inconsistencias de
snmp.py→ sub-área 2 (protocolos/probes).
Relacionado
- [[feature—blueprints—auditoria-s105]] — dominio anterior, mismo patrón.
- Doc maestro:
auditoria-suprema/AUDITORIA_SUPREMA.md(repo workspace). - Versión coloquial: [[concept—producto—observatory-mas-seguro]].
Véase también
- [[feature—monitoring—auditoria-suprema-sa6-observatory-realtime]]
- [[feature—monitoring—auditoria-sa2-s107]]
- [[feature—monitoring—auditoria-suprema-etapa-3-sa4-sa5]]