Ir al contenido

Blog ·

Un canario que corre contra servidores reales

Un simulador no puede probar la autenticación. Dos defectos de monitorización que solo servidores reales podían mostrar, y qué cuenta una página de estado.

Por Provibr

Una comprobación de monitorización que inicia sesión en una base de datos es un trozo pequeño de código de protocolo con una propiedad desagradable: casi toda forma de probarlo prueba lo que no toca.

La prueba evidente es un servidor simulado que habla el protocolo. Funciona, y demuestra bastante: el tramado, los tiempos de espera, qué ocurre si la conexión se cae en medio de un saludo, si un rechazo produce el código de error correcto. Lo que no puede demostrar es la autenticación, y por una razón estructural. Un simulador que recalcula nuestro propio revuelto usa nuestra propia implementación en los dos extremos del cable. Si se intercambian dos argumentos en un resumen, los dos lados los intercambian, y la prueba queda en verde sobre código que ningún servidor real aceptaría.

Ese hueco quedó anotado como asunto abierto al final de la ronda anterior de monitorización: SCRAM solo se había medido contra una implementación que también habíamos escrito nosotros. Un canario lo cierra.

Qué es aquí un canario

Un canario es un conjunto pequeño de pruebas que corren contra la cosa real en lugar de contra un modelo de ella. Contenedores desechables en nuestro propio servidor, atados a localhost, con un PostgreSQL 17, un Redis 7, un MySQL 8.4 y un MariaDB 11 auténticos dentro. Los adaptadores se invocan directamente y no a través del sobre de mandos, porque el asunto es el cable y no la fontanería que lo rodea. Once pruebas, todas omitidas por omisión, y cada una se niega a correr mientras su dirección no esté realmente definida.

Ese segundo cerrojo no sobra junto al primero. Si alguien corre la colección entera levantando la marca de omisión, una prueba conectaría en silencio a 127.0.0.1:0 y produciría un fallo que se lee como un error de nuestro código en vez de como una variable de entorno ausente.

Lo que cubren las once:

ServidorQué queda demostrado
PostgreSQL 17SCRAM con la contraseña correcta y con una equivocada, md5, trust, TLS exigido contra un servidor con TLS apagado, verificación completa de certificado contra un ancla ajena
Redis 7Autenticación por contraseña y un usuario con ACL, más la lista blanca de INFO
MySQL 8.4caching_sha2 con caché fría y con caché caliente, y una contraseña equivocada
MariaDB 11.8El mismo saludo contra otra implementación de él

La distinción entre caché fría y caliente importa porque son dos caminos de código distintos. MySQL usa en caching_sha2_password un revuelto barato en cuanto el servidor ya ha visto la cuenta, y pide autenticación completa la primera vez tras un reinicio. Solo un servidor real se reinicia.

Dos defectos que ningún simulador podía mostrar

PostgreSQL, contraseña equivocada. Con la contraseña equivocada, PostgreSQL no responde a la primera respuesta SASL con una continuación. Responde con un error que lleva el SQLSTATE 28P01, contraseña no válida. El adaptador tenía allí una desestructuración escueta, que convertía todo lo que no fuera una continuación en un error de protocolo: este servicio no ha respondido como un servidor PostgreSQL. El SQLSTATE se tiraba. Una clienta cuyas credenciales de monitorización habían caducado leía que su base de datos estaba rota.

Un simulador nunca pasa por esa rama, porque un simulador que prueba el camino feliz envía la continuación. Los dos puntos donde ahora puede llegar un error, tras el primer paso y tras el segundo, lo mantienen como fallo de autenticación.

Redis, un campo vacío. El adaptador lee exactamente dos campos de INFO: la versión y el rol. Pedía la sección server, y role no está en esa sección. Está en replication. El campo estaba vacío desde la ronda que lo introdujo, y nada lo decía, porque el simulador respondía con un bloque de INFO escrito por nosotros mismos en el que role aparecía amablemente donde lo esperábamos.

El arreglo son dos llamadas INFO separadas y no una sola que nombre ambas secciones, porque Redis solo admite varias secciones en un INFO a partir de la versión 7.0. En 6.x eso es un error de sintaxis, y como un INFO fallido deliberadamente no pone la comprobación en rojo, el campo habría vuelto a quedarse vacío en servidores antiguos. Una degradación silenciosa que sustituye a otra degradación silenciosa.

Los dos fallos comparten forma. Ninguno produjo un error. Uno produjo el error equivocado y el otro no produjo nada, y los dos eran invisibles para una colección de pruebas que estaba en verde.

MySQL, y la biblioteca que no se añadió

La misma ronda añadió MySQL y MariaDB como tipo de comprobación. Detrás no hay biblioteca de cliente, y eso es una repetición deliberada de una decisión anterior y no una decisión nueva.

Para PostgreSQL la elección había sido no escribir un SCRAM propio, porque SCRAM tiene un paso en el que el servidor firma algo que el cliente debería verificar, y una implementación que se salta ese paso queda en verde contra un servidor real e igual de verde contra uno falsificado. Ese es el atajo peligroso: nunca falla en voz alta.

MySQL no tiene tal paso. La autenticación nativa por contraseña es una cadena de resúmenes SHA1 y el camino rápido de caching_sha2_password es una cadena de resúmenes SHA256, y en ninguno de los dos firma el servidor nada cuya comprobación pudiéramos omitir. Queda el tramado más dos resúmenes, y las dos bibliotecas de resumen ya estaban en el árbol. Tampoco hacía falta un constructor de consultas: hay una consulta, SELECT 1, está como constante, y su respuesta esperada también.

Esto último se lee como una funcionalidad ausente. Sobre una consulta fija, una expectativa configurable es un botón en el que toda posición salvo la predeterminada hace fallar la comprobación, y un botón así enseña a quien administra que los botones no significan nada. Corresponde ponerlo el día en que exista una segunda consulta fija, y ni un día antes. La versión tampoco es una segunda consulta: MySQL la envía sin que se le pida en el paquete de saludo, y la segunda consulta es siempre aquella a la que dentro de un año le crece un campo de texto libre.

Hay dos cosas rechazadas de plano. La capacidad de cliente que permite a un servidor ordenar al cliente leer un fichero de su disco y subirlo no se solicita, y el byte de respuesta correspondiente es un error de protocolo en lugar de una rama que hace algo. Y la autenticación completa caching_sha2 solo se hace sobre TLS: la alternativa en una línea desnuda es pedir la clave pública del servidor por esa misma línea sin autenticar, y quien esté en medio responde con la suya. El rechazo nombra la reparación, que es encender TLS.

Una página de estado cuenta ticks, no resultados

La misma ronda puso una página de estado pública delante de todo esto, y la aritmética de esa página exigía una decisión que sale mal con facilidad.

Cada ejecución debida de una regla es un tick. Un tick puede terminar como up, degraded o down, y también puede terminar como source_unavailable (nuestra propia fuente de medición no estaba), configuration_error (la regla no tenía sentido), unsupported (el agente no conocía ese tipo de comprobación) o perdido. Esos cuatro últimos no dicen nada sobre el servicio de la clientela.

Si el denominador de una disponibilidad diaria fuera cada tick debido, una avería nuestra bajaría la cifra en la página de estado de la clientela. Por eso el denominador es up más degraded más down: los ticks en los que alguien miró de verdad el servicio. Si de eso no queda nada, la respuesta de ese día es que no hay respuesta, explícitamente ni cero ni cien por cien. En la página misma esos cuatro estados aparecen como desconocido y nunca como down.

Degraded cuenta la mitad. Es una elección y no una ley natural: un servicio que responde todo el día pero se mantiene por encima de su umbral de latencia no está caído ni está bien, y contarlo hacia cualquiera de los dos lados deja uno de los dos sin sentido.

Los huecos se muestran como huecos. Una regla creada la semana pasada no tiene noventa días de historia, y una barra que rellena eso con el valor del día vecino afirma algo que jamás se midió.

Lo que nos costó una colección de pruebas en verde

Cada uno de los defectos anteriores estaba debajo de una prueba superada. La colección de simuladores no estaba equivocada; respondía a una pregunta distinta de la que todo el mundo creía. El valor del canario no es que sea una prueba mejor. Es que es una prueba en la que el otro extremo del cable lo escribió alguien que no comparte nuestras suposiciones.

Eso vale seis contenedores desechables, y vale la pena escribir que son desechables: corren en nuestra propia máquina, se eliminan al terminar, y las pruebas se niegan a correr si las direcciones no están.

Del trabajo de monitorización de Provibr. Por qué una sonda alojada acepta exactamente un tipo de mensaje está en una sonda a la que no se puede convencer de nada, y por qué un secreto de monitorización viaja una sola vez está en un secreto que viaja una vez.

Empiece con un host y un agente

Cree una cuenta, instale el agente con un solo comando y conecte su primer host de Proxmox. El periodo de prueba dura 14 días y no pide ningún dato de pago.