CreaRack-SL

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)

  1. Gate de permisos — core.utils.require_perm(request, "observatory", view/edit) en targets.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.
  2. Credenciales SNMP fuera de la respuesta — MonitoringTargetOut serializaba el config completo (con snmp_community, snmp_v3_auth_key, snmp_v3_priv_key). Ahora resolve_config devuelve MonitoringTarget.public_config (strip por substring: community|password|auth_key|priv_key|secret|credential) + flag snmp_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.
  3. Inyección PromQL — metrics.py construía el metric_name VM concatenando claves de config['monitoring_oids']/['fast_poll_oids'] (controladas por el usuario) e interpolaba sin sanear en metrics_reader.get_generic_metric (f'last_over_time({metric_name}{{tenant_id="..."}}...')). Como el único aislamiento multi-tenant en VictoriaMetrics es el label tenant_id dentro 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.
  4. Guard org=None — require_org() en common.py lanza HttpError(403) en lugar del 500/AttributeError (que filtraba str(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 muestras packet_loss (era latency → sesgo, incluso >100%).
  • ping_targets.py: filter(monitor_type="ping") referenciaba un campo eliminado en migración 0003 (ahora @property) → FieldError. Cambiado a ping_enabled=True. (No estaba en ningún cron — dead code legacy, pero corregido.)
  • Helper notify_agents() reemplaza 6× except Exception: pass mudos (ahora loguean).
  • Helper _merge_vendor_oids() deduplica el bloque VendorProfile de /vm/extras y /vm/all (+ elimina un except: pass silencioso).
  • reachable unificado (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_create de los MetricSample en batch-snmp · .exists() redundante fuera en aggregate_metrics · clamp de rangos hours/days + slice en /bandwidth · validación de input en MonitoringTargetIn (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 organization en MetricSample/AggregatedMetric: hoy solo aislados por FK target a 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]]