EQProxy Load balancer y reverse proxy en Rust

API de administración

El plano de administración es opcional y vive en su propio socket y su propia tarea: el dataplane sigue sirviendo aunque esté caído o no se haya configurado. Esta página recoge los endpoints que la fuente documenta, sus roles, la matriz de autorización y los códigos de error.

Todo lo que hay aquí sale de la sección 9 de SPEC.md y de la matriz de autorización publicada con la evidencia de v0.8; lo que no aparece listado responde 404.

Endpoints

Método y rutaRolQué devuelve
GET /api/v1/statuslecturaVersión, uptime, topología validada, estado por servidor (ready, drain, maintenance), salud, intercambios en vuelo, contadores, revision, ETag y tls_fingerprint por listener
GET /metricslecturaTexto Prometheus: familia fija sin etiquetas, detalle opt-in y las cuatro series del presupuesto
GET /api/v1/cachelecturaEntradas de la caché de respuestas
PUT /api/v1/backends/{backend}/servers/{target}/stateescrituraCambia el estado de un servidor: ready, drain o maintenance
PUT /api/v1/backends/{name}escrituraSustituye la tabla [backend.*] del documento en caliente
PUT /api/v1/listeners/{name}escrituraSustituye la tabla [listener.*] del documento en caliente
POST /api/v1/certificates/reloadescrituraRelee los certificados y devuelve listener, listen, fingerprint, changed y quic por cada listener TLS
GET /panellectura o escrituraPanel web renderizado en servidor, si panel = true

La autenticación es siempre una cabecera Authorization: Bearer $TOKEN. Sin token, la respuesta es 401 con WWW-Authenticate: Bearer y no revela nada de la topología.

Roles y códigos

Hay dos secretos y dos niveles: el token de lectura abre las rutas GET; el de escritura abre además las escrituras. Si no hay token de escritura configurado, el plano entero es de solo lectura y toda escritura responde 403 write_disabled.

PeticiónAnónimoToken de lecturaToken de escritura
GET /api/v1/status401200200
GET /metrics401200200
GET /ruta-desconocida401404404
PUT …/servers/{target}/state401403200
PUT /api/v1/backends/{name}401403200 (503 sin aplicador)
PUT /api/v1/listeners/{name}401403200 (503 sin aplicador)
POST /api/v1/certificates/reload401403200 (503 sin aplicador)
DELETE /api/v1/status401405405

El 503 del arnés es deliberado: allí no hay aplicador conectado, y esa respuesta prueba que el token de escritura pasó el control de rol y la petición llegó al aplicador. Un 200 en esa casilla sería una promesa que la prueba no puede respaldar.

Otros códigos que hay que saber leer: 400 cuando el cuerpo no parsea o no valida, 409 cuando el candidato es válido pero el dataplane lo rechaza (conflicto de bind, TLS congelado en un listener QUIC), 412 con la revisión actual cuando el If-Match está obsoleto y 428 cuando falta. El 405 trae Allow: GET, PUT, POST, que describe los métodos que sirve el plano, no los que puede usar quien llama. Y ningún rechazo cambia el estado: la generación viva sigue igual.

Escrituras en caliente

Las dos PUT de tabla llevan como cuerpo la tabla TOML (Content-Type: application/toml), desnuda o copiada tal cual del fichero, con un máximo declarado de 64 KiB y sin cabecera Origin. Reemplazan la tabla y crean el nombre si no existía.

TOKEN=$(cat /etc/eqproxy/admin.token)
WRITE=$(cat /etc/eqproxy/admin-write.token)

# Revisión vigente (la que hay que devolver en If-Match)
curl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:9001/api/v1/status

# Sustituir la tabla del backend "api"
curl -s -X PUT \
  -H "Authorization: Bearer $WRITE" \
  -H "Content-Type: application/toml" \
  -H 'If-Match: "v1"' \
  --data-binary @backend-api.toml \
  http://127.0.0.1:9001/api/v1/backends/api

La respuesta es 200 solo después de que el dataplane haya hecho el cambio. Un listener que se conserva por nombre y dirección mantiene su socket, y las conexiones ya establecidas drenan en la generación con la que se aceptaron. Los cambios son solo de runtime: el fichero sigue siendo la base y el siguiente SIGHUP los reemplaza.

# Drenar un servidor (cuerpo JSON de 1 KiB como máximo)
curl -s -X PUT \
  -H "Authorization: Bearer $WRITE" \
  -H "Content-Type: application/json" \
  -d '{"state":"drain"}' \
  http://127.0.0.1:9001/api/v1/backends/api/servers/10.0.0.11:9000/state

# Renovar certificados: sin cuerpo y sin Origin
curl -s -X POST \
  -H "Authorization: Bearer $WRITE" \
  http://127.0.0.1:9001/api/v1/certificates/reload

Un servidor drenado no acepta peticiones nuevas, termina las que tiene en vuelo y sobrevive a un SIGHUP. maintenance es lo mismo más cerrar el material ocioso del slot —el pool keep-alive y la sesión HTTP/2 hacia el backend—, para no dejar nada del proxy aparcado en una máquina que se va a parchear.

Auditoría y límites

Cada intento escribe una línea AUDIT con el rol, el par, el método, la ruta, el estado y la transición; nunca el token. No hay sumidero duradero de auditoría ni identidades por usuario: solo los dos roles.

LímiteValor
Cuerpo de una escritura de topología64 KiB
Cuerpo de un cambio de estado1 KiB, application/json
Cabecera y duración de un intercambio8 KiB de cabecera, 5 s
Sesión del panel30 min de inactividad; como mucho 16 sesiones

El plano no tiene TLS en esta versión: allow_remote = true manda el token en claro. Para uso remoto, prefiere el socket Unix con modo 0600 o 0660 y allowlist de uids.