Todas las páginas
En esta página

Llegar al relay por un túnel SSH

El relay puede abrir una conexión SSH a un servidor tuyo y pedirle a ese servidor que reenvíe un puerto de vuelta al relay. Así llegas al relay en la dirección del servidor, y nada en casa escucha conexiones de internet. El relay arranca, vigila y reinicia el proceso ssh por sí mismo.

Elige esta opción cuando ya tengas un servidor con una dirección pública y acceso SSH. Si no tienes servidor, el Cloudflare Tunnel integrado no lo necesita.

Lo que necesitas #

  • El host del relay tiene un programa ssh en su PATH (o indicas otro con command, ver más abajo).
  • Un inicio de sesión SSH en el servidor que funcione con una clave y sin preguntar nada. El relay ejecuta ssh en modo batch, así que no puede escribir una contraseña ni la frase de contraseña de una clave. Una clave con frase de contraseña solo funciona si la tiene un agente SSH. Pruébalo antes desde el host del relay: ssh relay@relay.example.com debe iniciar sesión sin preguntar nada.
  • Un puerto libre en el servidor para el túnel (remotePort).
  • Algo en el servidor que sirva ese puerto por HTTPS: un servidor web o un proxy inverso como Caddy o nginx. El túnel lleva HTTP sin cifrar, así que sin eso tu token y tus conversaciones cruzan internet sin cifrar en cuanto el puerto es accesible. Consulta Ejecutar el relay detrás de un proxy inverso.

El relay no necesita ningún ajuste especial de sshd. Con remoteBind en loopback, pide el reenvío sin dirección de enlace, así que el propio ajuste GatewayPorts del servidor decide dónde se abre el puerto. Con su valor predeterminado, no, eso es solo el propio servidor, y el proxy inverso es lo único que da a internet. Con public el relay pide todas las interfaces (*:), y esa petición solo surte efecto si el sshd del servidor lo permite (GatewayPorts fijado a clientspecified o yes).

Configurarlo #

Añade un bloque sshTunnel a config.json (consulta Dónde está el archivo) y reinicia el relay con oar restart.

"sshTunnel": {
  "mode": "managed",
  "required": false,
  "user": "relay",
  "host": "relay.example.com",
  "remotePort": 4444,
  "remoteBind": "loopback",
  "identityFile": "~/.ssh/id_ed25519"
}

En el servidor, envía el HTTPS al puerto del túnel. Con Caddy:

relay.example.com {
    reverse_proxy localhost:4444
}

Abre https://relay.example.com/ en un navegador e inicia sesión con tu token de acceso. El banner de arranque del relay imprime una línea Remote: y oar url imprime una línea Tunnel URL: con un código QR. Ambas son https:// más el valor de host, sin puerto y sin comprobar que el túnel esté activo, así que solo son correctas cuando tu proxy sirve HTTPS en el puerto estándar.

Las claves de sshTunnel #

Todas las claves están en el bloque sshTunnel. La tabla coincide con config.json, que tiene la misma lista.

Clave Tipo Valor predeterminado Qué hace
mode "disabled" o "managed" "disabled" managed hace que el relay arranque y reinicie el túnel.
user cadena ninguno El usuario SSH en el servidor. Obligatorio en modo gestionado.
host cadena ninguno El nombre de host o la dirección del servidor. Obligatorio en modo gestionado.
remotePort número ninguno El puerto que se abre en el servidor, de 1 a 65535. Obligatorio en modo gestionado.
remoteBind "loopback" o "public" "loopback" loopback pide el puerto sin dirección de enlace (decide el GatewayPorts del servidor, consulta Lo que necesitas); public pide todas las interfaces. Cualquier otro valor cuenta como loopback. Independiente de localhostOnly.
identityFile cadena ninguno El archivo de la clave privada; ~ se expande. Sin él, ssh usa tu agente y sus claves predeterminadas.
command cadena ssh El programa SSH, que también se usa para la limpieza en el servidor. Un nombre se busca en el PATH; una ruta con barra se usa tal cual, y una relativa cuenta desde la carpeta del programa del relay.
required booleano false Mientras el túnel no está conectado, el relay no toma mensajes de la queue. Consulta Obligatorio.
autoReclaimPort booleano true Cuando el puerto remoto ya está ocupado, ejecuta una limpieza en el servidor y lo vuelve a intentar. Solo false lo desactiva.
reclaimStaleSshSessions booleano false Como parte de esa limpieza, termina también tus propias sesiones SSH inactivas en el servidor.
remoteCleanupCommand cadena integrado Tu propio comando de limpieza, que se ejecuta en el servidor en lugar del integrado.

La forma antigua "enabled": true sigue funcionando y significa "mode": "managed", pero solo cuando mode no está fijado a disabled ni a managed.

El relay reenvía el remotePort del servidor a su propio puerto en 127.0.0.1 (3333 salvo que hayas cambiado port). Con public el reenvío se pide como *:remotePort, y si no, como remotePort.

Estos ajustes son fijos y no tienen claves: el relay ejecuta ssh -N con ServerAliveInterval=30, ServerAliveCountMax=3, BatchMode=yes, StrictHostKeyChecking=accept-new y ExitOnForwardFailure=yes. ssh da por perdida una conexión tras tres comprobaciones sin respuesta, con 30 segundos entre ellas.

Si falta un valor obligatorio (user, host o un remotePort válido en modo gestionado), el relay arranca como siempre, registra cuál falta y deja el túnel desactivado.

Cómo se comporta #

Arranque. El relay arranca el túnel en cuanto está escuchando. Cuenta el túnel como conectado cuando ssh lleva 1,2 segundos en ejecución y el servidor no ha rechazado el reenvío. El registro dice entonces Tunnel up to user@host remote port N.

Reconexión. Cuando ssh termina, el relay lo vuelve a arrancar tras una espera que crece: 5, 10, 20, 40 y luego 60 segundos, y se queda en 60. La cuenta vuelve a empezar cuando se cae una conexión que duró más de 30 segundos. Detener el relay detiene el túnel y los reintentos.

Claves de host. Un servidor que el host del relay no ha visto nunca se acepta y se recuerda en la primera conexión. Después, una clave de host cambiada hace que ssh se niegue a conectarse hasta que quites la entrada antigua de known_hosts.

Obligatorio #

Con required: true la queue queda en pausa desde el arranque hasta que el túnel está conectado, y otra vez cada vez que se cae. Los mensajes que envías esperan en la queue y se ejecutan en cuanto el túnel está activo. Úsalo cuando el relay no te sirva sin el túnel. Un túnel que no puede arrancar porque falta un valor también mantiene la queue en pausa.

Cuando el puerto remoto está ocupado #

Si el servidor dice que no se puede enlazar el puerto, la causa habitual es una sesión sobrante de un túnel anterior que el servidor no ha detectado como muerta. Salvo que autoReclaimPort sea false, el relay se conecta entonces al servidor una segunda vez y ejecuta allí un comando de limpieza.

Estas opciones ejecutan comandos en tu servidor

La limpieza se ejecuta con tu inicio de sesión SSH y mata procesos. Termina cualquier proceso de ese usuario que escuche en el puerto remoto, sea o no un túnel sobrante. Con reclaimStaleSshSessions termina también todas las sesiones SSH inactivas de ese usuario, lo que incluye otros túneles ssh -N -R del mismo inicio de sesión en otros puertos. remoteCleanupCommand ejecuta lo que escribas. Lee lo que hace cada paso antes de dejarlas activadas.

La limpieza integrada hace esto, en orden:

  1. Comprueba si el puerto está en uso (con ss o, si no, leyendo la lista de sockets a la escucha del kernel). Si no lo está, se detiene e informa de éxito.
  2. Busca los procesos que escuchan en el puerto (con lsof o, si no, fuser, el que exista), les envía una señal de terminación (kill) y espera un segundo. Un proceso al que tu inicio de sesión no puede enviar señales se queda. Si el puerto ya está libre, informa de éxito.
  3. Solo con reclaimStaleSshSessions: true, y solo si el puerto sigue en uso: envía una señal de terminación a cada sesión sshd de tu usuario que no tenga procesos hijos y espera un segundo. Son las sesiones que deja atrás un ssh -N -R. Las sesiones que ejecutan una shell, el servidor remoto de un editor o cualquier otro programa tienen hijos y se omiten, igual que la sesión que ejecuta la propia limpieza.
  4. Si el puerto sigue en uso, la limpieza termina con el código 3, que el relay registra como is still held.

Con remoteCleanupCommand fijado, tu comando se ejecuta en lugar de todo eso, y reclaimStaleSshSessions no tiene efecto. El relay lo ejecuta como sh -lc '<command>' a través de un segundo inicio de sesión ssh con la misma clave, en modo batch. Debe terminar con 0 cuando el puerto está libre y con otro código cuando sigue ocupado.

Tras una limpieza que liberó el puerto, el relay lo reintenta al cabo de un segundo, hasta tres veces seguidas. Tras una limpieza fallida, o con autoReclaimPort desactivado, usa la espera normal descrita arriba.

Ver el estado del túnel #

El túnel SSH no se muestra en los sitios donde aparece el túnel de Cloudflare: oar status y oar doctor solo informan del túnel de Cloudflare, y el punto de estado no se vuelve ámbar con un túnel SSH (consulta El punto de estado). Mira aquí en su lugar:

  • El registro del relay. Cada línea empieza por [ssh-tunnel]. Muestra la línea de comandos de ssh que ejecuta, lo que imprime ssh, Tunnel up to ..., Reconnecting in Ns (attempt N)... y los pasos de la recuperación del puerto. Dónde está el registro depende de cómo se ejecute el relay; consulta Archivos y carpetas.
  • El banner de arranque, con una línea Remote: y una línea Tunnel: mode=loopback (o public) cuando el túnel gestionado está configurado. Muestran la configuración, no si el túnel está activo. oar url imprime la misma dirección como Tunnel URL:.
  • /api/status, que tiene un objeto sshTunnel con connected, connectedSince, reconnectAttempts, lastError, blocking, required, mode, valid, host, remotePort y remoteBindMode. Consúltalo con tu token.

lastError es exit:<code> cuando ssh ha terminado, remote-forward-bind-failed:<code> cuando el servidor ha rechazado el puerto, invalid-config o el mensaje del valor que falta cuando el bloque está incompleto, y vacío mientras el túnel está activo.

Seguridad #

Una vez activo el túnel, se puede llegar al relay desde internet, y el token de acceso es lo único que se interpone entre un desconocido y tus agentes, que ejecutan comandos en tu máquina. Por eso:

  • Mantén remoteBind en loopback y pon HTTPS delante en el servidor. Con public, cualquiera que llegue al puerto del túnel en el servidor llega al relay por HTTP sin cifrar.
  • El token de acceso es el único cerrojo. Consulta El token de acceso y quién llega al relay y Qué expone OAR.
  • El relay rechaza las solicitudes a sus rutas internas del worker de sesión cuando llevan la cabecera marcadora de Cloudflare cf-ray o una cabecera que listes en tunnelMarkerHeaders. Un proxy inverso normal no añade ninguna de ellas, así que lista la cabecera que añade tu proxy, si añade una que quieras bloquear. Consulta config.json.

Solución de problemas #

Mira primero las líneas [ssh-tunnel] del registro. Las líneas que empiezan por stderr: son lo que imprimió el propio ssh.

Lo que ves Qué significa Qué hacer
sshTunnel.user is required when sshTunnel.mode is "managed" (también para host) Falta un valor obligatorio. El túnel queda desactivado. Fíjalo y ejecuta oar restart.
sshTunnel.remotePort must be a positive integer when sshTunnel.mode is "managed" Falta el puerto o no es un número de 1 a 65535. Corrígelo y reinicia.
Tunnel mode disabled; running direct relay only. mode no es managed. Fija "mode": "managed".
stderr: ... Permission denied (publickey) El servidor no aceptó tu clave. El modo batch implica que no se pide contraseña. Fija identityFile y prueba el mismo ssh user@host a mano en el host del relay.
Process exited (code=N). Scheduling reconnect... repetido ssh no pudo conectarse o iniciar sesión. Ejecuta a mano el comando ssh registrado y lee su error.
stderr: ... remote port forwarding failed for listen port N, y luego Attempting remote reclaim ... El puerto del servidor está en uso. Espera a la recuperación. Si termina con is still held, libera el puerto tú mismo o elige otro remotePort.
Remote reclaim finished: listen port N is still held (code=3) La limpieza no pudo liberar el puerto. Busca en el servidor quién lo ocupa, prueba reclaimStaleSshSessions: true o fija remoteCleanupCommand.
stderr: ... Host key verification failed o REMOTE HOST IDENTIFICATION HAS CHANGED La clave del servidor no coincide con la guardada. Si el cambio es esperado, quita la entrada antigua de known_hosts. Si no, no te conectes.
Reconnecting in 60s (attempt N) una y otra vez, y el túnel nunca dice Tunnel up La conexión falla cada vez. Lee las líneas stderr: anteriores a cada una.
El túnel dice Tunnel up pero la dirección no responde El lado del relay está bien; el del servidor, no. Comprueba que el proxy inverso esté en marcha y apunte al remotePort, y que remoteBind encaje con cómo llega a él el proxy.
El túnel se conecta, se cae y se reconecta en bucle Algo en el servidor o en la red termina la conexión. El registro muestra el código de salida y los mensajes de ssh. Revisa el registro de sshd del servidor.
Los mensajes se quedan en la queue required es true y el túnel no está conectado. Arregla el túnel o fija required a false.