¿Cómo llegar a una base de datos que solo escucha en localhost?
Una instancia de Postgres en un servidor remoto está enlazada a 127.0.0.1 a propósito. Nada en la red puede alcanzarla, solo los procesos que corren en esa misma máquina. Tu portátil no es esa máquina, así que psql -h remote-server -p 5432 falla antes de llegar siquiera a la autenticación. Abrir el puerto al mundo anula la razón por la que se enlazó al loopback en primer lugar.
Lo que quieres es tomar prestada, durante unos minutos, la vista que la máquina remota tiene de 127.0.0.1, desde tu propia terminal. SSH ya tiene una conexión cifrada y autenticada abierta entre las dos máquinas en el momento en que inicias sesión. El port forwarding reutiliza esa conexión para llevar tráfico distinto al de tu shell.
¿Cómo funciona el port forwarding de SSH por dentro?
Una sesión SSH normal parece un único tubo: las teclas entran, la salida de la terminal vuelve. Por debajo, el protocolo multiplexa varios canales independientes sobre esa única conexión cifrada. La shell es un canal. Una conexión TCP redirigida es otro.
Cuando le pides a ssh que redirija un puerto, abre un canal nuevo por cada conexión que llega a ese puerto, transporta los bytes en crudo a través de la sesión existente y los entrega al otro lado. Sin certificado nuevo, sin segundo login. El túnel hereda el acceso que ya tenga el login SSH.
Ten esto presente antes de mirar los flags. -L, -R y -D no son tres mecanismos distintos. Dirigen el mismo mecanismo de channel-forwarding en direcciones diferentes.
¿Cómo se usa el port forwarding local de SSH (-L)?
| |
Lee el argumento de -L como local_port:destination_host:destination_port. Esto abre el puerto 5432 en tu portátil. Cualquier cosa que se conecte a él se redirige a través del túnel hacia localhost:5432 tal como lo ve db-host, que es la instancia de Postgres enlazada a su propio loopback. En una segunda terminal:
| |
psql cree que habla con una base de datos local. En realidad habla con la interfaz de loopback de db-host a través de SSH.
El host de destino, la parte del medio de esa sintaxis, no tiene por qué ser el propio servidor SSH:
| |
Esto redirige a través de jumpbox hacia una tercera máquina que jumpbox puede alcanzar y tú no. Es la forma que la mayoría de la gente tiene en mente cuando dice “túnel SSH”: una máquina con el acceso que a ti te falta, redirigiendo hacia una red que de otro modo no podrías tocar. Si ya tienes Postgres corriendo en local, el puerto 5432 de tu portátil está ocupado; elige otro puerto local como 15432 y pasa -p 15432 a psql.
¿Cómo exponer un puerto local con remote forwarding de SSH (-R)?
-R hace lo mismo al revés. La sintaxis es remote_port:destination_host:destination_port, pero el listener se abre en el lado remoto.
| |
Ahora public-host escucha en el puerto 8080, y cualquier conexión a él vuelve a través del túnel hacia localhost:3000 en tu máquina: tu servidor de desarrollo local. Consigues una dirección alcanzable para un proveedor de webhooks o un compañero sin abrir un puerto en tu propio firewall, tomando prestada una máquina que ya tiene uno.
Hay un comportamiento por defecto que suele confundir. sshd enlaza los puertos abiertos con -R a la interfaz de loopback del host remoto, así que curl public-host:8080 desde una tercera máquina falla aunque el forward esté activo. Solo los procesos en el propio public-host pueden alcanzarlo. Cambiar esto requiere GatewayPorts yes en el sshd_config de ese servidor, un ajuste que solo controla el administrador del servidor. Ningún flag en tu comando ssh lo sobrescribe.
¿Cómo convertir un túnel SSH en un proxy SOCKS (-D)?
-L y -R redirigen cada uno a un destino fijo, decidido al ejecutar el comando. -D no elige ningún destino:
| |
Esto abre un proxy SOCKS en localhost:1080. Cualquier aplicación configurada para usarlo elige el destino en cada conexión, y ssh la retransmite desde la posición de remote-host en la red:
| |
La parte -hostname importa. Con el simple --socks5, curl resuelve internal-service.corp en tu portátil, donde ese nombre no existe, y falla antes de que el proxy llegue a usarse. --socks5-hostname envía el nombre a través del túnel para que sea remote-host quien lo resuelva.
Los navegadores funcionan igual una vez que configuras el proxy en sus ajustes de red (y activas “proxy DNS when using SOCKS v5” en Firefox). Cada sitio ve entonces la IP de remote-host y pasa por sus reglas de firewall. Así es como llegas a toda una red interna a través de un solo jump box, o compruebas qué sirve un sitio a otra región, sin escribir un forward distinto para cada destino.
¿Qué tipo de forwarding de SSH elegir: -L, -R o -D?
-L local | -R remoto | -D dinámico | |
|---|---|---|---|
| El listener se abre en | Tu máquina | El host remoto | Tu máquina |
| Destino | Fijo, fijado en la línea de comandos | Fijo, fijado en la línea de comandos | Elegido por conexión por la app |
| Uso típico | Llegar a un servicio que solo ve el host remoto | Exponer tu servicio local al lado remoto | Enrutar tráfico arbitrario a través de la red del host remoto |
| Necesita en el otro extremo | Nada más que un login SSH normal | GatewayPorts si otros deben alcanzarlo | Nada más que un login SSH normal |
¿Cómo mantener un túnel SSH abierto en segundo plano?
Aparecen dos problemas enseguida: escribir el comando completo cada vez, y que el túnel muera al cerrar la terminal. ~/.ssh/config resuelve el primero:
| |
Ahora ssh db-tunnel abre ambos forwards a la vez. LocalForward, RemoteForward y DynamicForward corresponden a -L, -R y -D, una línea por cada forward, tantas como necesites en un bloque Host.
Para un forward que debe permanecer activo sin supervisión, añade -N (no ejecutar ningún comando remoto, solo mantener el túnel) y -f (pasar a segundo plano tras autenticarse). Súmale un keepalive para que una conexión caída en silencio se detecte, en lugar de quedarse colgada:
| |
Aun así, esto no se reconecta cuando cae la red. ssh termina y el forward desaparece. autossh envuelve el mismo comando y lo reinicia cuando la conexión muere:
| |
-M 0 desactiva el puerto de monitorización propio de autossh y deja que el keepalive ServerAlive* detecte una conexión muerta. En OpenSSH moderno, esa es la configuración a usar.
¿Cuándo no conviene usar túneles SSH?
Un puerto redirigido hereda el acceso del login SSH que lo abrió. Cómodo cuando eres tú quien tiene la clave, y una exposición real si la clave o la sesión se ven comprometidas, porque el túnel regala todo lo que ese login puede alcanzar. AllowTcpForwarding en sshd_config es el interruptor del lado del servidor (por defecto yes). Ponerlo en no cierra esta vía, pero cualquiera con acceso shell a la máquina puede seguir lanzando su propio forwarder, así que solo ayuda si también restringes el acceso shell.
Los túneles también dejan de escalar más allá de un puñado de personas o servicios. Cada uno es un proceso atado a una terminal o a una instancia de autossh. No hay un lugar central donde ver quién tiene un túnel abierto, revocar el acceso sin tocar cada configuración cliente, o aplicar una sola política a todos los usuarios. En ese punto, una VPN, o un proxy consciente de la identidad delante del servicio, debería reemplazar el montón de túneles personales.
Tampoco son la misma herramienta que un reverse proxy o Cloudflare Tunnel, aunque los tres hacen alcanzable algo que antes no lo era. Un reverse proxy se sitúa permanentemente delante de un servicio y da la cara a internet público. Cloudflare Tunnel está más cerca de -R: una conexión saliente desde tu lado que se vuelve alcanzable desde fuera, pero a través del edge de Cloudflare, con un hostname estable y sin puerto de servidor que gestionar. Y mientras que un reverse proxy o Cloudflare Tunnel dependen de certificados, como se explica en cómo funciona el handshake de TLS, un túnel SSH no necesita ninguno. El cifrado es la propia sesión SSH, autenticada por tu clave en lugar de por un certificado en el que confiaría un navegador.
¿Qué configurar primero?
Coge el túnel que más repites y conviértelo hoy en un bloque Host en ~/.ssh/config. Llegar a algo que solo ve un host remoto es -L. Exponer un servicio local a través de una máquina con dirección pública es -R. Enrutar el tráfico de una aplicación a través de una red remota sin elegir destinos de antemano es -D. Una vez en el config, ssh -N -f db-tunnel es todo lo que tienes que escribir, y dejas de equivocarte de número de puerto a las 11 de la noche.