Article

Un punto de acceso de estado que es general en público y detallado en privado

Cómo exponer un punto de acceso de salud que da al público un resumen general activo/degradado mientras desbloquea el detalle completo por servicio solo para un llamador de servidor a servidor que posee un secreto compartido. Cubre la comparación en tiempo constante, el almacenamiento en caché privado, los valores predeterminados que fallan en modo cerrado y un despliegue seguro ante el orden.
July 14, 2026
Topics:ObservabilitySecurity
Tags:Next.jsDocker

Una página de estado pública es útil, y también un regalo para los atacantes: el estado activo/caído por servicio, la latencia, los nombres de host internos y las IP son un mapa en vivo de su infraestructura. Queríamos la señal de salud sin el reconocimiento. Este es el patrón.

El problema

Nuestro front end desacoplado muestra una pequeña franja de estado. Consultaba un punto de acceso /api/system-status de Drupal que devolvía detalle por servicio a todo el mundo: Postgres, Redis, Solr, la malla, la monitorización, el almacenamiento, hasta una IP interna de la LAN. Un llamador anónimo obtenía un inventario completo de servicios y un mapa de interrupciones.

La forma

Dos audiencias, un punto de acceso:

  • Anónimo recibe un resumen general: { status: "ok" | "degraded", time }. Suficiente para mostrar un punto verde o rojo. Sin nombres de servicio, sin latencia, sin direcciones.
  • Un llamador autorizado recibe el desglose completo por servicio.

La cuestión es cómo demuestra el front end que está autorizado sin enviar una credencial al navegador.

De servidor a servidor, no de navegador a servidor

El navegador nunca ve el secreto. El propio servidor del front end renderiza la franja: consulta Drupal desde el servidor, adjunta un secreto compartido en una cabecera y pasa al cliente solo el resultado general. El secreto vive en el entorno del servidor, nunca en un paquete de código.

// front-end server -> Drupal (secret stays server-side)
fetch(statusUrl, { headers: { "x-status-secret": process.env.STATUS_SECRET } })

Drupal desbloquea la respuesta detallada cuando la cabecera coincide —comparada en tiempo constante— o cuando el llamador tiene el permiso de administrador. Todos los demás reciben el resumen.

$detailed = $user->hasPermission('administer site configuration')
         || hash_equals($configured, $provided);

Detalles que importan

  • Comparación en tiempo constante. Use hash_equals() (o crypto.timingSafeEqual), nunca ==, para que el secreto no pueda recuperarse midiendo tiempos.
  • Mantenga privada la respuesta detallada. Márquela como no-store y varíe según la cabecera del secreto, para que una caché compartida nunca sirva el detalle a una solicitud anónima.
  • Seguro ante el orden de despliegue. Hasta que el secreto esté presente en ambos lados, el punto de acceso devuelve el resumen general. Una cabecera que Drupal aún no acepta, o un secreto que el front end aún no envía, se degradan ambos a un resultado seguro para el público, de modo que los cambios de back end, front end e infraestructura pueden lanzarse en cualquier orden.
  • Falla en modo cerrado. Sin secreto configurado, el detalle está desactivado para todos, no activado.

El resultado

La Internet pública ve un solo bit honesto: activo o degradado. Los operadores y el servidor del front end lo ven todo. El mapa de infraestructura nunca sale del límite de confianza, y la señal de salud sigue funcionando.