Implementación de Reenvío de Puertos SSH Local y Remoto para Túneles
Desbloquea el acceso seguro a la red y la evasión de cortafuegos utilizando el reenvío de puertos SSH. Esta guía completa detalla la implementación práctica de las técnicas de tunelización SSH Local (`-L`) y Remota (`-R`). Aprende la sintaxis esencial, comprende las diferencias clave entre acceder a servicios remotos y exponer servicios locales, y ve ejemplos claros para tareas como asegurar conexiones de bases de datos o compartir entornos de desarrollo. Incluye las mejores prácticas críticas para crear túneles de fondo persistentes y seguros utilizando autenticación basada en claves.
Implementación de Reenvío de Puertos SSH Local y Remoto para Túneles
El reenvío de puertos SSH es una de esas herramientas que olvidas hasta que un cortafuegos, una subred privada o una red incómoda de un proveedor bloquea el camino simple. Entonces se convierte en la solución limpia más rápida. Puedes usarlo para alcanzar una base de datos a través de un host bastión, probar una página de administración privada desde tu portátil, o exponer un servidor de desarrollo local a una máquina que no puede alcanzar tu portátil directamente.
La idea básica es simple: SSH abre un puerto de escucha en un lado de la conexión y transporta el tráfico a través de la sesión SSH cifrada a un destino en el otro lado. La parte que confunde a la gente es la dirección. El reenvío local con -L permite que tu máquina alcance algo cerca del servidor SSH. El reenvío remoto con -R permite que algo cerca del servidor SSH alcance algo cerca de tu máquina.
Reenvío local: lleva un servicio remoto a tu portátil
Usa el reenvío local cuando el servicio que necesitas es accesible desde el servidor SSH, pero no desde tu estación de trabajo.
ssh -L 15432:db.internal.example:5432 [email protected]
Después de que esto se conecte, tu portátil escucha en 127.0.0.1:15432. Cuando apuntas psql, DBeaver o una configuración de aplicación a ese puerto local, SSH envía el tráfico a bastion.example.com, y el bastión abre una conexión a db.internal.example:5432.
Lee el comando de izquierda a derecha:
puerto local en mi máquina : host de destino visto por el servidor SSH : puerto de destino
Ese "visto por el servidor SSH" importa. Si la base de datos se llama db.internal.example solo dentro de la red privada, tu portátil no necesita resolver ese nombre. El bastión lo hace. Si la base de datos solo escucha en localhost en el bastión, usa esto en su lugar:
ssh -L 15432:127.0.0.1:5432 [email protected]
El reenvío local suele ser el valor predeterminado más seguro porque el puerto de escucha está en tu estación de trabajo. Por defecto, OpenSSH vincula los puertos reenviados localmente a la interfaz de loopback, por lo que otras máquinas en tu Wi-Fi o red de oficina no pueden usar el túnel. Puedes ser explícito:
ssh -L 127.0.0.1:15432:db.internal.example:5432 [email protected]
Evita vincular a 0.0.0.0 a menos que realmente quieras compartir el túnel con otros hosts. Un comando como este convierte tu portátil en un proxy hacia la base de datos privada para cualquiera que pueda alcanzar el puerto 15432 en tu portátil:
ssh -L 0.0.0.0:15432:db.internal.example:5432 [email protected]
Eso puede ser útil en un laboratorio. Rara vez es lo que quieres en una estación de trabajo normal.
Reenvío remoto: expone un servicio local a través del servidor
El reenvío remoto invierte el lado de escucha. Úsalo cuando un servicio se está ejecutando en tu máquina, pero alguien o algo cerca del servidor SSH necesita alcanzarlo.
ssh -R 18080:127.0.0.1:3000 [email protected]
Esto le pide a public.example.com que escuche en el puerto 18080. Las conexiones a ese puerto se llevan de vuelta a través de SSH a 127.0.0.1:3000 en tu portátil. Esto es útil cuando estás probando un receptor de webhook, compartiendo una demostración temporal, o depurando una devolución de llamada de un sistema de staging que no puede llamar directamente a tu portátil.
Hay una sorpresa común: los puertos reenviados remotamente generalmente se vinculan a loopback en el servidor SSH por defecto. Eso significa que curl http://127.0.0.1:18080 funciona cuando se ejecuta en public.example.com, pero http://public.example.com:18080 desde tu navegador puede que no.
Para hacer que un puerto reenviado remotamente sea accesible desde otras máquinas, el servidor SSH debe permitirlo. En /etc/ssh/sshd_config, la configuración relevante es comúnmente:
GatewayPorts clientspecified
Entonces puedes solicitar un enlace público:
ssh -R 0.0.0.0:18080:127.0.0.1:3000 [email protected]
Usa esto con cuidado. Estás publicando un servicio local a través del servidor. Pon un cortafuegos delante, usa un puerto alto aleatorio, y no expongas herramientas de administración, bases de datos de desarrollo o aplicaciones no autenticadas a internet.
Mantén los túneles aburridos y confiables
Para un túnel de larga duración, normalmente no quieres un shell en el host remoto:
ssh -N -L 15432:db.internal.example:5432 [email protected]
-N significa "no ejecutar un comando remoto". Agrega keepalives cuando el túnel cruce NAT, VPN o balanceadores de carga en la nube que cierren sesiones TCP inactivas:
ssh -N \
-o ServerAliveInterval=30 \
-o ServerAliveCountMax=3 \
-L 15432:db.internal.example:5432 \
[email protected]
Para uso desatendido, prefiere un servicio de usuario systemd, autossh o tu supervisor de procesos sobre un comando ssh -f desnudo. Ejecutar en segundo plano con -f funciona, pero hace que los inicios fallidos y los túneles obsoletos sean más difíciles de notar.
ssh -fN -L 15432:db.internal.example:5432 [email protected]
Si usas -fN, prueba el mismo comando sin -f primero. Los avisos de contraseña, los avisos de clave de host desconocida y los conflictos de puertos son mucho más fáciles de diagnosticar en primer plano.
Lista de verificación de solución de problemas
Cuando un túnel se conecta pero la aplicación aún falla, verifica cada salto en lugar de adivinar.
Primero, confirma que el listener local existe:
ss -ltnp | grep 15432
Luego prueba el destino desde el servidor SSH:
ssh [email protected] 'nc -vz db.internal.example 5432'
Si eso falla, el reenvío SSH no es el problema. El bastión no puede alcanzar el servicio, el nombre no se resuelve allí, un grupo de seguridad bloquea el puerto, o el servicio está vinculado a la interfaz incorrecta.
Si el listener no se inicia, el puerto local puede estar ya en uso:
lsof -iTCP:15432 -sTCP:LISTEN
Si el reenvío remoto falla con un mensaje como remote port forwarding failed, el servidor puede bloquear el reenvío TCP. Verifica AllowTcpForwarding en sshd_config, y verifica si el puerto solicitado ya está ocupado.
Hábitos de seguridad que vale la pena mantener
Usa autenticación basada en claves y restringe la cuenta utilizada para los túneles. Para un usuario de túnel dedicado, puedes combinar acceso limitado al shell, reglas de cortafuegos y opciones de SSH como PermitOpen o PermitListen dependiendo de la dirección que necesites. Esos controles evitan que un túnel de conveniencia se convierta en un acceso amplio a la red.
Nombra los túneles en tus notas o runbooks por intención, no solo por comando. "Portátil 15432 a réplica de informes de producción a través del bastión" es más fácil de auditar que una línea misteriosa ssh -L en el historial del shell.
El reenvío local te ayuda a alcanzar hacia adentro. El reenvío remoto permite que otros te alcancen de vuelta. Una vez que esa distinción está clara, la mayoría de los problemas de tunelización SSH se convierten en una cuestión de verificar qué lado escucha, qué lado resuelve el destino y qué cortafuegos se encuentra entre ellos.
Algunos patrones reales que verás
Un patrón común de producción es el túnel de mantenimiento de base de datos. Tienes una réplica de informes en una subred privada, un host bastión con acceso SSH estricto y una herramienta de analista en tu portátil. El reenvío local encaja limpiamente:
ssh -N -L 127.0.0.1:15432:reporting-db.internal:5432 [email protected]
La aplicación en tu portátil debe usar 127.0.0.1, no el nombre de host de la base de datos privada. Si la herramienta insiste en la verificación del nombre de host SSL contra el host de la base de datos, es posible que necesites conectarte con el nombre de host de la base de datos y agregar una entrada de hosts local, o configurar el cliente con el modo SSL correcto. El túnel solo mueve bytes TCP; no reescribe los detalles del protocolo de la base de datos.
Otro patrón es un receptor de webhook temporal:
ssh -N -R 127.0.0.1:19090:127.0.0.1:9090 [email protected]
En esa versión, el puerto reenviado es intencionalmente utilizable solo desde la propia puerta de enlace. Entonces podrías configurar un servicio de staging en la puerta de enlace para llamar a http://127.0.0.1:19090/hook. Esto es más seguro que publicar el puerto a toda la red.
Para una demostración pública corta, usa un enlace público solo después de agregar una regla de cortafuegos:
ssh -N -R 0.0.0.0:19090:127.0.0.1:3000 [email protected]
Luego restringe la puerta de enlace:
sudo ufw allow from 203.0.113.40 to any port 19090 proto tcp
Sin esa restricción, cualquiera que pueda alcanzar la puerta de enlace puede intentar el servicio reenviado.
Lo que el tunelización SSH no resuelve
El reenvío SSH no es un sustituto de la autenticación del servicio. Si la base de datos acepta conexiones locales sin contraseña, un túnel puede extender ese límite de confianza débil más allá de lo previsto. Si una aplicación de desarrollo local no tiene página de inicio de sesión, el reenvío remoto puede publicarla tal cual.
Tampoco hace que un destino inestable sea confiable. Si el bastión no puede resolver el nombre interno, si el servicio está caído, o si un grupo de seguridad bloquea la ruta, el túnel puede establecerse con éxito mientras la aplicación falla. Por eso es tan útil probar desde el servidor SSH.
Finalmente, los túneles son fáciles de olvidar. Un túnel obsoleto en un host de salto compartido puede dejar un puerto inesperado abierto. Para cualquier cosa de larga duración, pon el comando en un archivo de servicio con un nombre claro, propietario y política de reinicio. Para cualquier cosa temporal, ciérralo cuando el trabajo esté hecho y verifica que el listener desapareció con ss -ltnp.