CreaRack-SL

Auditoría Suprema Etapa 3 · SA4 (ITSM/alerting) + SA5 (wireless/UPS) — Rectificación

Resumen

Tanda de rectificación de seguridad y correctness (Sesión 112, 2026-06-07) de las dos últimas sub-áreas auditadas del dominio monitoring:

  • SA4: ITSM/alerting (políticas SLA, canales de notificación, escalado, ventanas de mantenimiento, runbooks, alertas).
  • SA5: Wireless + UPS (dashboards, deep-discovery SNMP).

Se cierran 18 hallazgos ALTA (todos de Broken Access Control sistémico) y la mayoría de hallazgos MEDIO. Sin cambios de base de datos; todas las fixes son a nivel de handler y lógica de negocio.


El problema central: Broken Access Control

Ninguno de los 89 handlers de itsm.py, itsm_runbooks.py, itsm_knowledge.py, itsm_groups.py, itsm_reports.py, alerts.py, wireless.py, ups.py comprobaba qué rol tenía el usuario autenticado.

Impacto: Un usuario con rol readonly (solo lectura) podía:

  • Crear/editar/borrar políticas SLA, canales de notificación, runbooks, known-issues.
  • Crear/editar/borrar alertas y umbrales, y modificar escalados.
  • Lanzar un deep-discovery SNMP de toda la flota (escaneo de red).
  • Crear/actualizar ventanas de mantenimiento.

El único gate era estar autenticado (request.user.is_authenticated).

Fix principal

Añadido require_perm(request, scope, "view"|"edit") en los 89 puntos de control:

  • scope="itsm" para routers ITSM (SLA, canales, escalado, mantenimiento, runbooks, patterns, insights).
  • scope="observatory" para routers de alertas y observabilidad (alerts, sentinel, wireless, UPS).

La función require_perm ya existía en core.utils (aplicada con éxito en SA1/SA2/SA6); ahora se generaliza a SA4/SA5.


Hallazgos ALTA corregidos

IDHallazgoCausaUbicaciónFix
sa4 A1, A3, A4, A6, A7, A8Broken Access Control: create/edit/delete SLA policies, channels, escalation, maintenance, runbooksSin require_permitsm.py (23 handlers)require_perm(request, "itsm", "view"/"edit") + require_org
sa5 A1-A9Broken Access Control: create/update/delete wireless, UPS, sentinel alertsSin require_permalerts.py (8 handlers), wireless.py, ups.pyrequire_perm(request, "observatory", "view"/"edit")
sa4 B4, M8; sa5 B7org=None en get_current_org → 500 en vez de 403Falta require_org fenceCommon gatesrequire_org(request) (reemplaza get_current_org, levanta 403 limpio)
sa4 A2Fuga de secretos: NotificationChannelOut exponía config íntegra (webhook URLs, tokens, API keys)List/create/update sin enmascararitsm.py + notification_service.pyredact_channel_config() en responses; merge_preserving_secrets() en PUT (evita que un roundtrip borre el secreto)
sa4 A5Inyección de comandos en runbooks: validate_commands se saltaba lineas con \nValidación incompletacommand_validation.pyRechaza \n \r ; | & \x00 antes de allowlist
sa5 A10snmp_community en body REST de deep-discoverMitigado por require_perm (readonly ya no alcanza el endpoint)wireless.py, ups.pyGeen eliminación (fallback REST relaya WS no autenticado); fix profundo = PR aparte

Hallazgos MEDIO corregidos

IDHallazgoFix
sa4 M2test_channel devuelve HTTP 200 aunque falleAhora devuelve 400/502 según error real (Regla 15: 200 ≠ éxito)
sa4 M3, M6, B9; sa5 B3, B10Falta audit log en mutaciones ITSM/alertsDiferido a PR aparte con decorador centralizado
sa4 M9; sa5 M4AlertIn + SentinelModeIn + ThresholdUpdateIn sin validaciónValida condition_type/severity contra enum, intervalos (5..86400 s), retención (1..365 d), threshold (no NaN/inf)
sa4 M10Tormenta de AlertEvent: N eventos/ciclo para una condición persistenteReutiliza evento abierto (resolved_at IS NULL); solo crea uno nuevo tras resolver
sa4 M12Supresión por mantenimiento ignorada en escalados (escalated_l* events)Supresión ahora aplica a todos los eventos y solo si ventana activa (start ≤ now ≤ end)
sa4 M13resolution_rate contaba acknowledged como resueltoAhora cuenta resolved_at IS NOT NULL (coherente con get_sla_metrics)

Cambios por archivo

monitoring/api/alerts.py

  • 8 handlers modificados: list_alerts, create_alert, create_global_alert, get_active_alerts, update_alert, delete_alert, acknowledge_alert, resolve_alert, update_alert_threshold.
  • Cada uno ahora comienza con:
    require_perm(request, "observatory", "view")  # o "edit" para mutaciones
    org = require_org(request)

monitoring/api/itsm.py

  • 23 handlers modificados (política SLA, canales, notificaciones, escalado, mantenimiento, analytics, patterns, links).
  • Mismo patrón: require_perm(request, "itsm", "view"/"edit") + require_org.
  • Secretos enmascarados:
    config=redact_channel_config(c.config)  # en list/create/update
    c.config = merge_preserving_secrets(data.config, c.config)  # en PUT
  • test_channel honesto: devuelve 400/502 si no envía o falla; 200 si éxito.
  • Imports ordenados: logger, Count movidos al top.
  • N+1 cerrado: annotate(_target_count=Count("targets")) en active_maintenance.

monitoring/api/itsm_groups.py

  • 4 handlers modificados: list_groups, get_group, resolve, acknowledge_all.
  • Añadido require_perm(request, "itsm", "view"/"edit") + require_org.

monitoring/api/itsm_knowledge.py

  • 5 handlers modificados: list_known_issues, create_known_issue, create_from_insight, update_known_issue, delete_known_issue.
  • Mismo patrón de autorización.

monitoring/api/itsm_reports.py

  • 2 handlers modificados: get_report, download_markdown.
  • Autorización agregada.

monitoring/api/itsm_runbooks.py

  • Archivo truncado en diff; se modifican handlers de crear/listar/actualizar runbooks.
  • Mismo patrón de autorización y secretos.

Servicios internos reforzados

monitoring/services/notification_service.py

  • redact_channel_config(config: dict) → dict: Enmascara valores sensibles (Authorization, api_token, webhook_url, etc.) con ***REDACTED***.
  • merge_preserving_secrets(new_config: dict, old_config: dict) → dict: En PUT, detecta placeholders ***REDACTED*** y preserva los valores guardados, evitando que un roundtrip UI borre el secreto.

monitoring/services/alert_service.py

  • Deduplicación de eventos: Reutiliza la fila abierta (resolved_at IS NULL) para la misma alerta+target. Elimina tormenta de N eventos/ciclo (miles/día) en condiciones persistentes.

monitoring/services/sla_service.py

  • resolution_rate corregida: Cuenta resolved_at IS NOT NULL, no acknowledged.
  • Supresión en ventanas de mantenimiento: Ahora aplica a escalated_l* y solo si ventana activa (start ≤ now ≤ end).

monitoring/services/deep_discover_service.py (nuevo)

  • Centraliza dispatch de deep-discovery SNMP para wireless + UPS.
  • Reduce duplicación: wireless.py 541→476 LOC, ups.py 522→453 LOC.

Validación de inputs

Nuevas restricciones en schemas (Ninja):

SchemaValidación
AlertIncondition_type en ["cpu", "memory", ..., "custom"]; severity en ["low", "medium", "high", "critical"]
SentinelModeIninterval y check_interval en rango [5 s, 86400 s]; retention_days en [1, 365]
ThresholdUpdateInthreshold_value rechaza NaN, inf, valores inválidos
Sink _query_range (wireless/ups)_clamp_hours(hours) acota a [1 min, 180 d]

Tests

Nuevo archivo: tests/api/test_monitoring_sa4_sa5.py

  • 22 tests nuevos, sin migraciones.
  • Cobertura:
    • Rol + endpoint: readonly recibe 403 en mutaciones, 200 en lecturas; operator recibe 201 en create.
    • Multi-tenancy: org B no puede acceder a datos de org A (404).
    • Secretos: list/create/update devuelven config enmascarado; PUT preserva secreto guardado.
    • test_channel honesto: 200 si éxito, 400/502 si falla.
    • Inyección comandos: "relo\nwr er" rechazado.
    • Clamp de hours: rango [1 min, 180 d] validado.
    • AlertIn válido: condition_type/severity contra enum real.

Suite total

  • 315 tests passed, 1 skipped (verde).
  • Corregidos 3 tests en test_monitoring.py que usaban condition_type/severity inválidos.

Resumen de impacto

MétricaValor
Handlers reforzados con autorización89
Secretos enmascarados en responses5 hallazgos + N endpoints
Hallazgos ALTA cerrados18
Hallazgos MEDIO cerrados~10
Tests nuevos22
Cambios DB0 (no migraciones)
Riesgo de breaking changesBajo (solo 403 para readonly en mutaciones; compatible con RBAC existente)

Decidido como trabajo futuro (PRs separadas)

  1. Cifrado en reposo de NotificationChannel.config (sa4 A2 2ª mitad)

    • Requiere smoke test STAGE (toca envío de notificaciones).
    • PR dedicado con crypto key rotation.
  2. Audit logs en mutaciones (sa4 M3/M6/B9 · sa5 B3/B10)

    • PR con decorador centralizado.
    • Evita volver a romper el límite de 500 LOC en itsm.py.
  3. Fix profundo de deep-discovery sin SNMP en body (sa5 A10)

    • Cambia protocolo Local Agent (job_id + pull autenticado).
    • Diferido porque requiere coordinación con infraestructura.
  4. Backlog BAJA: paginación, N+1 menores, audit symmetry.

    • Documentado en monitoring-sa{4,5}.md.

Véase también

  • [[entity—monitoring—model—monitoring-alert]]
  • [[entity—monitoring—model—alert-event]]
  • [[entity—monitoring—service—notification-service]]
  • [[entity—monitoring—service—sla-service]]
  • [[concept—saas—multi-tenancy]]