CreaRack-SL

UI local del Agent y terminal con Bearer enforcement (Agent 2.1.3)

Resumen

Corrección de dos superficies de la UI local del Agent que quedaron inaccesibles tras activar el enforcement del Bearer local (v2.1.2):

  1. Página de diagnóstico local (localhost:5050/metrics/ui, /licenses) — servidas sin sesión del SaaS, no pueden llevar token. Se eximen seis rutas (dos páginas + cuatro GET de estado).
  2. Iframe del terminal — ahora recibe y envía el Bearer recibido por postMessage, permitiendo que el fetch a /terminal/sessions se autentique.

Resultado: Fase 3 (Raíz 3) CERRADA. La política fail-closed intacta: acciones y GETs sensibles siguen protegidas.


Problema y contexto

Con el enforcement del token local activado en Agent 2.1.2 (cierre de la Raíz 3 de la Auditoría Suprema), la mayoría de endpoints pasaron a exigir Authorization: Bearer <token>. Sin embargo, dos superficies que el propio Agent muestra en el ordenador del cliente (localhost:5050) quedaron devolviendo 401:

  1. Página de diagnóstico del Agent (assets/debug.html, metrics.html):

    • Se sirven sin sesión del SaaS — el cliente accede vía navegador local sin JWT.
    • No tienen forma de presentar un token (no son javascript que intercepte SaaS auth).
    • Consumían GET de solo lectura (/sentinel/status, /traps/stats, /metrics/stats).
    • Aparecía error: “Sentinel status not available” aunque el servicio funcionaba.
  2. Iframe del terminal (assets/terminal.html):

    • Empotraba un fetch a /terminal/sessions sin header de autenticación.
    • El SaaS sí le pasaba el token local vía postMessage(SET_AUTH_TOKEN), pero el iframe no lo usaba.

Solución (Agent 2.1.3)

Exempt paths en local_token.py

Se añadieron 6 rutas a EXEMPT_PATHS:

EXEMPT_PATHS = frozenset({
    # ... bootstrap existente ...
    "/metrics/ui",          # Página HTML de métricas locales
    "/licenses",            # Página de licencias
    "/licenses.txt",        # Endpoint de descarga de licencias
    "/sentinel/status",     # GET estado del Sentinel (lo-lectura)
    "/traps/stats",         # GET estadísticas de traps
    "/traps/recent",        # GET eventos recientes de traps
    "/metrics/stats",       # GET estadísticas de métricas
    # ... resto de exentos ...
})

Política: todas son GET de solo lectura consumidas por la propia UI local. El estado del Sentinel ya se expone en /info, por lo que no se añade lógica nueva — solo se abre lo que ya existía.

Terminal iframe y Bearer en terminal.html

El fetch a /terminal/sessions ahora añade el header Authorization:

// Antes:
fetch(`/terminal/sessions`).then(r => r.json())...

// Después:
fetch(`/terminal/sessions`, 
  localAuthToken ? { headers: { Authorization: `Bearer ${localAuthToken}` } } : undefined
).then(r => r.json())...

El localAuthToken ya se recibía vía postMessage(SET_AUTH_TOKEN) — ahora se usa.

Política fail-closed intacta

Todas las acciones y GETs sensibles siguen detrás del token:

RutaProtección
/cluster/*✅ Bearer obligatorio (RCE)
/network/*✅ Bearer obligatorio (SSRF/scan)
/sftp/*✅ Bearer obligatorio (acceso de ficheros)
/signage/*✅ Bearer obligatorio
/admin/uninstall✅ Bearer obligatorio
/sentinel/start|stop|reset✅ Bearer obligatorio
/metrics/purge*✅ Bearer obligatorio
/terminal/sessions✅ Bearer obligatorio (iframe la envía)
/terminal/session/*✅ Bearer obligatorio

Cambios detallados

1. terminal/agent/core/local_token.py

  • EXEMPT_PATHS += 8 rutas: páginas UI local + GET de estado.
  • Documentación reforzada: se especifica por qué se eximen (bootstrap, páginas local, GET de solo lectura).
  • Raíz 3 en comentario: POLÍTICA (fail-closed) enumera qué sí está protegido.

2. terminal/agent/assets/terminal.html

  • Línea ~390: fetch(/terminal/sessions) incluye localAuthToken en headers si está disponible.
  • Formato: .replace() (no .format()) — las llaves JS quedan escapadas.

3. terminal/agent/version.py

  • Versión: 2.1.2 → 2.1.3.
  • Changelog detallado: qué se exime y por qué.

4. tests/agent/test_agent_local_auth.py

  • test_exempt_paths_cover_bootstrap(): nuevas rutas en assertion.
  • Confirmación: /terminal/sessions, /sentinel/start, /metrics/purge NO se eximen.

Validación

  • ✅ 7 tests verdes.
  • ✅ ruff limpio.
  • ✅ Testeado en Docker.
  • ✅ RELEASE_NOTES actualizado con pasos de Edu (build_agent.bat → smoke → publish).

Implicaciones

  • Usuarios: No ven cambios — la UI local vuelve a funcionar normalmente.
  • Seguridad: Raíz 3 cierre sin regresiones — el iframe del terminal sigue protegido.
  • Operaciones: Fase 3 (audit local) CERRADA. Próxima: Fase 4.

Véase también

  • [[entity—terminal—model—agent-instance]]
  • [[entity—terminal—service—local-token]]
  • [[concept—security—bearer-token]]
  • [[concept—terminal—local-api]]
  • [[feature—terminal—enforcement-local-token]]