Todas las páginas
En esta página

Ejecutar el relay detrás de un proxy inverso

Un proxy inverso es un servidor web propio que recibe la conexión del navegador y se la pasa al relay. Lo necesitas cuando quieres HTTPS para un relay en tu LAN o en un servidor, cuando el relay debe vivir bajo una subruta de un sitio que ya tienes (https://example.com/oar/), o cuando el edge de un túnel (por ejemplo, Cloudflare) se sitúa entre el navegador y el relay. Un proxy en la propia máquina del relay funciona sin ningún ajuste. Un proxy en otra máquina necesita dos o tres, que se muestran más abajo. Cinco claves de config.json informan al relay sobre el proxy, y cada una resuelve algo concreto; se explican después.

Qué debe hacer el proxy #

La página del relay, su API y su conexión en vivo usan un único puerto (3333 salvo que hayas cambiado port). El proxy debe:

  • Dejar pasar los upgrades a WebSocket. La app mantiene abierta una conexión Socket.IO con el relay, en la ruta /socket.io/ (con un prefijo, <prefix>/socket.io/). Si los upgrades no pasan, la página carga pero nunca muestra actualizaciones en vivo.
  • No almacenar en búfer ni cortar por tiempo las conexiones de larga duración. Ese socket permanece abierto mientras la página esté abierta, y los turns se transmiten por él a medida que ocurren. Usa tiempos de espera de lectura e inactividad largos, y no almacenes las respuestas en búfer.
  • Dejar pasar la cabecera Host, o enviar X-Forwarded-Host. El relay construye a partir de ella la dirección de un enlace compartido y Nuevo en 0.9.11 acepta la conexión en vivo solo desde una página en esa dirección (consulta allowedOrigins). X-Forwarded-Host y X-Forwarded-Proto solo cuentan si vienen de un proxy en el que el relay confía.
  • Enviar X-Forwarded-Proto cuando el proxy termina TLS, para que el relay sepa que el navegador usó HTTPS (consulta Cookies y HTTPS), y X-Forwarded-For, para que conozca la dirección del cliente.
  • Mantener la ruta tal cual. No la reescribas. Si sirves el relay bajo un prefijo, el relay quita el prefijo por sí mismo (consulta remotePath).

No necesitas ninguna regla de caché. El relay envía Cache-Control: no-store para su página, su service worker, su manifiesto y su API, y también CDN-Cache-Control: no-store y Cloudflare-CDN-Cache-Control: no-store. No añadas una regla que almacene en caché /api/shared/*: las vistas compartidas consultan periódicamente si hay contenido nuevo, y una respuesta en caché mostraría a los espectadores una instantánea antigua.

Un ejemplo mínimo de Caddy para un relay en la misma máquina. Caddy deja pasar los upgrades a WebSocket por sí mismo y añade X-Forwarded-Proto:

relay.example.com {
    reverse_proxy 127.0.0.1:3333
}

Para nginx o cualquier otro proxy, consulta en su propia documentación cómo dejar pasar los upgrades y fijar los tiempos de espera, y compáralo con la lista de arriba. Para los túneles, consulta Túnel SSH y Cloudflare Tunnel; para el token de acceso, consulta Acceso.

Un proxy en otra máquina Nuevo en 0.9.11 #

Los túneles integrados y un proxy en la propia máquina del relay llegan al relay por 127.0.0.1, y el trustProxy predeterminado se fía de sus cabeceras reenviadas. Un proxy en otro host, o en otro contenedor o máquina virtual, es un interlocutor distinto. Hasta que confíes en él, el relay ignora sus X-Forwarded-For, X-Forwarded-Host y X-Forwarded-Proto, así que:

  • todos los visitantes tienen la dirección del proxy: un solo bloqueo de inicio de sesión para todos ellos, y una sola dirección en cada entrada del registro de accesos;
  • los enlaces compartidos nuevos empiezan por http:// y el host que envió el proxy;
  • la cookie de inicio de sesión no se marca como Secure.

Fija tres claves en el archivo de configuración y reinicia el relay:

{
  "localhostOnly": false,
  "trustProxy": "192.0.2.10",
  "allowedOrigins": ["https://relay.example.com"]
}
  • localhostOnly: false deja que el relay escuche más allá de loopback. Mantén el puerto cerrado a todo salvo al proxy.
  • trustProxy es la dirección del proxy, su subred o el número de proxies delante del relay (1 para uno). Consulta más abajo.
  • allowedOrigins solo hace falta cuando el proxy reescribe la cabecera Host y no envía X-Forwarded-Host. Consulta más abajo.

Un proxy en la propia máquina del relay no necesita ni trustProxy ni localhostOnly: false, y allowedOrigins solo si reescribe Host. Bajo un prefijo de URL, fija también remotePath.

Las claves que informan al relay sobre el proxy #

Las cinco están en la tabla de config.json. Reinicia el relay después de cambiarlas.

trustProxy #

Predeterminado "loopback". Decide de qué cabeceras X-Forwarded-* se fía el relay. Con el valor predeterminado, solo se fía de un proxy que se conecta desde la misma máquina, lo cual es lo correcto para Caddy, nginx o cloudflared ejecutándose junto al relay.

Lo que el relay toma de las cabeceras de un proxy de confianza:

  • si la solicitud llegó por HTTPS, lo que decide si la cookie de inicio de sesión lleva el indicador Secure, y el esquema de un enlace compartido nuevo;
  • el host al que se dirigió el navegador (X-Forwarded-Host), para los enlaces compartidos nuevos y para la comprobación del origen de la conexión en vivo;
  • la dirección del cliente, para el bloqueo de inicio de sesión, el límite de presencia de las vistas compartidas y el registro de accesos de los enlaces compartidos. El país de ese registro (cf-ipcountry) también se lee solo de un proxy de confianza.

Si tu proxy se ejecuta en otra máquina, el relay ve un interlocutor no local e ignora esas cabeceras. Dale a trustProxy lo que acepta trust proxy de Express: un número de saltos (1), una dirección o subred ("10.0.0.0/8"), una lista de ellas o "loopback".

Solo con un proxy delante

Fija trustProxy más amplio que "loopback" solo cuando todas las conexiones al relay pasen de verdad por tu proxy. Si no, cualquiera que llegue al puerto puede enviar un X-Forwarded-Proto falsificado.

Nuevo en 0.9.11 Todo uso de una cabecera reenviada sigue a trustProxy. En 0.9.10, el límite de presencia de las vistas compartidas y la dirección de un enlace compartido nuevo las leían de cualquier cliente.

allowedOrigins Nuevo en 0.9.11 #

Predeterminado []. La conexión en vivo (Socket.IO) acepta el handshake de un navegador solo desde el propio origen del relay: el Origin que envía el navegador debe tener el host al que se dirigió el navegador, que es X-Forwarded-Host cuando un proxy de confianza lo envía y Host en caso contrario. Una página de otro sitio no puede usar tu cookie de inicio de sesión para abrir una conexión en vivo. Los clientes que no envían Origin, como los scripts, no se ven afectados.

Un proxy que reescribe la cabecera Host con la dirección interna del relay y no envía X-Forwarded-Host rompe esa comparación. En ese caso, lista el origen público exactamente como lo muestra el navegador, con esquema, host y cualquier puerto distinto del predeterminado:

"allowedOrigins": ["https://relay.example.com"]

Sin él, la página carga y puedes iniciar sesión, pero nada se actualiza en vivo, y el relay registra una vez por origen:

[socket] Refused a handshake from origin https://relay.example.com (host 127.0.0.1:3333); list it in allowedOrigins if a proxy rewrites the Host header

remotePath #

Predeterminado "". Fíjalo al prefijo cuando el proxy sirve el relay bajo una subruta, por ejemplo "/oar". Se añade una barra inicial y se quitan las barras finales.

Con él fijado, el relay:

  • quita el prefijo de cada solicitud entrante, así que el proxy pasa la ruta con el prefijo y no lo elimina;
  • pone el prefijo en la ruta de la cookie de inicio de sesión, la ruta del socket (/oar/socket.io/) y las direcciones de archivos y enlaces compartidos.

Sin él, un relay bajo un prefijo no encuentra sus propios archivos ni su socket, porque la página pide /socket.io/ y /api/... en la raíz del sitio. Abre el relay con la barra final (https://example.com/oar/).

Una app instalada pertenece a la dirección desde la que se instaló, así que un relay bajo un prefijo es una app propia. Consulta Instalar como app.

publicHostnames #

Predeterminado []. Lista los nombres de host que llegan al propio relay, por ejemplo ["relay.example.com"]. El relay los usa en un único sitio: la preview lane rechaza un previews.publicBaseUrl en uno de esos nombres (o en localhost o 127.0.0.1), para que una app en vista previa nunca comparta una cookie de inicio de sesión con el relay. Si pones el relay detrás de un proxy con un nombre nuevo y usas vistas previas, añade ese nombre aquí.

tunnelMarkerHeaders #

Predeterminado []. Los nombres de las cabeceras que tu proxy o el edge de tu túnel añade a cada solicitud que reenvía. El relay los usa para la protección de rutas del worker de sesión descrita abajo. cf-ray, que añade Cloudflare, está siempre en la lista. El valor puede ser un array o una cadena separada por comas. La variable de entorno COPILOT_TUNNEL_MARKER_HEADERS (separada por comas) añade más nombres; Nuevo en 0.9.11 las dos listas se combinan, así que tunnelMarkerHeaders: [] ya no desactiva la variable. Consulta Variables de entorno.

El punto de estado

tunnelMarkerHeaders no vuelve ámbar el punto de estado. El punto está ámbar mientras el túnel de Cloudflare gestionado por el propio relay está conectado (consulta el menú de la conversación). Un proxy que ejecutas tú nunca lo vuelve ámbar.

La protección de rutas del worker de sesión #

Cada sesión de agente se ejecuta en un worker en la máquina del relay, y el worker habla con el relay por un WebSocket en /api/session-worker/ws (con un prefijo, <prefix>/api/session-worker/ws). Un proxy o un túnel reenvía al relay todas las rutas de su nombre de host, así que sin una protección esa ruta sería accesible desde internet. Solo deben usarla los workers locales.

Nuevo en 0.9.11 La protección deja pasar una solicitud a esa ruta solo cuando viene de la propia máquina del relay y nada la ha reenviado:

  • el interlocutor que se conecta es loopback (127.0.0.1 o ::1);
  • no lleva ninguna cabecera marcadora: cf-ray, o una de tunnelMarkerHeaders;
  • no lleva X-Forwarded-For, Forwarded ni X-Real-IP.

Todo lo demás se rechaza antes de comprobar el inicio de sesión:

  • una solicitud normal recibe 403 con {"error":"Session worker endpoints are reachable from this machine only"};
  • un upgrade a WebSocket recibe 403 Forbidden y la conexión se cierra.

Cada rechazo se registra como [tunnel-guard] Blocked session-worker path …: not local (…), indicando qué delató la solicitud. Los workers locales se conectan a 127.0.0.1 directamente, sin ninguna de esas cabeceras, y no se ven afectados. Los enlaces compartidos siguen funcionando a través del túnel: la protección se basa solo en la ruta del worker, no en que una solicitud sea anónima.

Cloudflare añade cf-ray y Caddy añade X-Forwarded-For por sí mismo, así que la protección funciona con ellos sin ningún ajuste; con nginx, envía X-Forwarded-For con proxy_set_header. Un reenviador que no añade nada en absoluto, como un túnel SSH inverso o un proxy TCP simple, parece un proceso local; la ruta queda entonces protegida solo por el token de acceso. Con un reenviador así, haz que añada una cabecera con un valor fijo no vacío, por ejemplo X-Oar-Edge: 1, y lista su nombre: "tunnelMarkerHeaders": ["x-oar-edge"]. O rechaza la ruta delante del relay.

Endpoints que solo puede usar la máquina del relay #

Nuevo en 0.9.11 Dos cosas solo responden a solicitudes de la propia máquina del relay que no haya reenviado ningún proxy, con la misma regla que la protección de rutas:

  • el socket del worker de sesión, descrito arriba;
  • la ejecución de una operación de imagen (POST /api/image-operations/…/execute), a la que llaman los propios workers del relay. Todo lo demás recibe 403 Image operation execution is reachable from this machine only (La ejecución de operaciones de imagen solo es accesible desde esta máquina).

Todo lo demás solo necesita el token de acceso, venga de donde venga. Eso incluye 🌄 Restart web relay (Reiniciar el relay web), retirar un reinicio en cola con el botón Cancel (Cancelar) de su banner, y 🚮 Empty queue (Vaciar la cola): cualquier navegador con sesión iniciada puede usarlos, también por la LAN y a través de cualquier proxy o túnel.

Hasta 0.9.10 estas tres cosas estaban pensadas solo para la máquina del relay, pero la comprobación dejaba pasar todo lo que llegaba por un túnel o un proxy en la misma máquina, y rechazaba una conexión LAN directa mientras la página informaba de éxito. Si los usuarios remotos no deben reiniciar el relay ni vaciar su queue, haz que el proxy rechace POST /api/relay/shutdown, POST /api/relay/shutdown/cancel y POST /api/queue/empty. oar restart en la máquina del relay siempre funciona.

Cookies y HTTPS #

La cookie de inicio de sesión es HttpOnly y SameSite=Lax, dura 30 días y tiene la ruta / (o el prefijo de remotePath). Lleva Secure cuando el relay considera que la solicitud es segura.

  • El proxy termina TLS en la misma máquina. El proxy envía X-Forwarded-Proto: https, el trustProxy predeterminado se fía de él y la cookie es Secure.
  • El proxy termina TLS en otra máquina. Fija trustProxy como se indica arriba. Hasta que lo hagas, la cookie no tiene el indicador Secure, aunque sigue funcionando.
  • Configuraciones mixtas. Si también abres el relay directamente por http sin cifrar en la LAN, esa solicitud no es segura, así que su cookie no tiene el indicador Secure; las solicitudes HTTPS a través del proxy sí lo reciben. Los navegadores guardan las cookies por dirección.

Comprobar la configuración #

  1. Abre la dirección del proxy en un navegador. Aparece el inicio de sesión o la lista de conversaciones.
  2. Mira el punto de estado: se vuelve verde y la página muestra actualizaciones en vivo cuando inicias un turn. Un estado de reconexión sin fin significa que el WebSocket no pasa.
  3. Bajo un prefijo, abre la dirección con la barra final. Cargan la página, sus estilos y la conexión en vivo, y la ruta de la cookie es el prefijo (míralo en las herramientas de desarrollo del navegador).
  4. Crea un enlace compartido (Compartir). Su dirección usa el nombre de host público y https, y el prefijo si fijaste uno.
  5. Prueba la protección desde fuera: pide https://relay.example.com/api/session-worker/ws. Deberías ver 403 y una línea [tunnel-guard] en el registro del relay. Si recibes otra respuesta, tu proxy no añade ni una cabecera marcadora ni X-Forwarded-For.
  6. Detrás de un proxy en otra máquina: inicia sesión desde dos dispositivos y comprueba en Settings → Sharing (Ajustes → Compartir) que el registro de accesos de un enlace compartido muestra las direcciones de los espectadores, no la del proxy. Si muestra la del proxy, trustProxy no coincide con el proxy.

Si no funciona #

Lo que ves Causa probable
La página carga pero se reconecta sin fin, o nunca se actualiza El proxy no deja pasar los upgrades a WebSocket en /socket.io/, o cierra las conexiones inactivas.
La página carga pero nada se actualiza en vivo, y el registro dice Refused a handshake from origin El proxy reescribe la cabecera Host. Lista el origen público en allowedOrigins, o haz que el proxy deje pasar Host.
El inicio de sesión dice Too many failed sign-in attempts (Demasiados intentos de inicio de sesión fallidos) a todo el mundo a la vez El relay no confía en un proxy en otra máquina, así que todos los visitantes comparten su dirección y su bloqueo. Fija trustProxy.
Bajo un prefijo, la página está en blanco o faltan archivos remotePath no está fijado, o el proxy elimina el prefijo. El proxy debe pasarlo.
Bajo un prefijo, se te pide iniciar sesión una y otra vez La ruta de la cookie es el prefijo de remotePath, así que el navegador solo la envía bajo ese prefijo. Abre el relay bajo ese prefijo y con la barra final.
Los enlaces compartidos o de archivos tienen una dirección incorrecta o no tienen prefijo remotePath no está fijado, o el proxy no envía Host ni X-Forwarded-Host / X-Forwarded-Proto, o el relay no confía en el proxy.
Los enlaces compartidos empiezan por http://, o la cookie no tiene el indicador Secure, detrás de un proxy en otra máquina trustProxy sigue en "loopback".
La ruta del worker responde sin un 403 a través de tu proxy El proxy no añade ni una cabecera marcadora ni X-Forwarded-For. Consulta la protección de rutas.
El punto nunca se vuelve ámbar Es lo esperado: solo lo hace el túnel de Cloudflare gestionado por el propio relay.
Las vistas previas no arrancan con el nombre nuevo Añade el nombre de host del relay a publicHostnames.

Para el primer inicio de sesión y el token de acceso, consulta Primer arranque.