Lo hice y lo entendí

El blog de Vicente Navarro
24 may

Creando túneles TCP/IP (port forwarding) con SSH: Los 8 escenarios posibles usando OpenSSH

La función típica del protocolo de red Secure Shell (SSH) es acceder en modo terminal a un sistema remoto y ejecutar allí comandos de forma segura gracias a que los datos van cifrados. Pero además, a través de esa conexión de datos segura, es posible crear túneles (reenviar puertos / port forwarding) entre los extremos conectados de forma que las conexiones TCP/IP se encauzan a través de la conexión SSH con lo que podemos conseguir saltarnos cualquier firewall o bloqueo de puertos siempre que tengamos la posibilidad de conectar con SSH.

Como este tema está muy tratado por toda la red:

en esta entrada no entraremos en los detalles del reenvío de puertos, sino que pretende ser una chuleta, una referencia rápida (cheat sheet) de cómo reenviar puertos TCP con OpenSSH en los 8 diferentes escenarios que se pueden dar. Otros clientes de SSH como PuTTY también permiten el reenvío de puertos, pero la configuración se hará con un interfaz gráfico. Nosotros nos centraremos en OpenSSH.

En los siguientes ejemplos y situaciones supondremos que tenemos una red externa y una red interna y entre ambas redes, la única conexión posible es una conexión SSH entre el nodo de la red external externo1 y el nodo de la red interna interno1. El nodo externo2 está en la red externa y tiene conectividad total con externo1. El nodo interno2 está en la red interna y tiene conectividad total con interno1.

Túneles SSH: Sin túnel

Escenario 1: Usar en externo1 un servicio TCP ofrecido por interno1 (Local port forwarding / bind_address=localhost / host=localhost)

El sistema externo1 se puede conectar al sistema interno1 a través de OpenSSH y además quiere conectarse al servidor de VNC (puerto 5900) del sistema interno1:

Túneles SSH: Escenario 1

Lo conseguiremos con este comando:

externo1 $ ssh -L 7900:localhost:5900 usuario@interno1

Ahora en el sistema externo1 podemos comprobar que el puerto 7900 está esperando conexiones:

externo1 $ netstat -ltn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
... 
tcp        0      0 127.0.0.1:7900            0.0.0.0:*               LISTEN  
...   

Sólo necesitamos ejecutar ahora en externo1:

externo1 $ vncviewer localhost::7900

para conectarnos al servidor VNC de interno1.

Nota: Esta forma de cambiar el puerto del no está documentada en el man vncviewer. Aparece en: About VNCViewer configuration of the output TCP port. También es así como lo hace el vncviewer de TightVNC.

Escenario 2: Usar en externo2 un servicio TCP ofrecido por interno1 (Local port forwarding / bind_address=0.0.0.0 / host=localhost)

Esta vez partimos de una situación similar a la anterior pero ahora queremos que sea externo2 quien se conecte al servidor VNC de interno1:

Túneles SSH: Escenario 2

El comando adecuado sería este:

externo1 $ ssh -L 0.0.0.0:7900:localhost:5900 usuario@interno1

Es similar al del primer escenario; pero en aquél, si nos fijamos en la salida del netstat, el puerto 7900 se había asociado a la dirección de localhost, a la 127.0.0.1, por lo que sólo procesos locales podían conectarse a él. Esta vez especificamos que el puerto se asocie a 0.0.0.0, para que el sistema acepte conexiones a cualquier IP local de la máquina:

externo1 $ netstat -ltn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
...
tcp        0      0 0.0.0.0:7900            0.0.0.0:*               LISTEN
...

De modo que ahora, desde externo2, podemos ejecutar:

externo2 $ vncviewer externo1::7900

para conectarnos al servidor VNC de interno1.

En lugar de especificar la IP 0.0.0.0, también podríamos usar la opción -g (Allows remote hosts to connect to local forwarded ports) así:

externo1 $ ssh -g -L 7900:localhost:5900 usuario@interno1

con exactamente el mismo resultado que el comando anterior:

externo1 $ ssh -L 0.0.0.0:7900:localhost:5900 usuario@interno1

Por otra parte, si hubiéramos querido restringir la conexión a sólo alguna de las IPs locales del sistema, podríamos haber sido más específicos:

externo1 $ ssh -L 192.168.24.80:7900:localhost:5900 usuario@interno1

externo1 $ netstat -ltn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
...  
tcp        0      0 192.168.24.80:7900            0.0.0.0:*               LISTEN
...

Escenario 3: Usar en interno1 un servicio TCP ofrecido por externo1 (Remote port forwarding / bind_address=localhost / host=localhost)

En el primer escenario, era el propio sistema con el servidor de SSH el que ofrecía otro servicio. Ahora el sistema con el cliente de SSH es el que ofrece el servicio que el sistema con el servidor de SSH quiere usar:

Túneles SSH: Escenario 3

El comando que usaremos es el mismo que en el primer escenario cambiando el parámetro -L por -R:

externo1 $ ssh -R 7900:localhost:5900 usuario@interno1

Y ahora donde veremos que tenemos el puerto 7900 escuchando es en interno1:

interno1 $ netstat -lnt
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
...  
tcp        0      0 127.0.0.1:7900          0.0.0.0:*               LISTEN
...

De modo que ahora desde interno1 podemos usar el servidor VNC de externo1 así:

interno1 $ vncviewer localhost::7900

Escenario 4: Usar en interno2 un servicio TCP ofrecido por externo1 (Remote port forwarding / bind_address=0.0.0.0 / host=localhost)

Similar al tercer escenario pero ahora, igual que hacíamos en el segundo escenario, asociaremos el puerto reenviado a la IP 0.0.0.0 para que otros nodos puedan usar el servicio:

Túneles SSH: Escenario 4

El comando apropiado es:

externo1 $ ssh -R 0.0.0.0:7900:localhost:5900 usuario@interno1

Sin embargo, es importante entender que, por motivos de seguridad, esto no funcionará si en la configuración del servidor de SSH no modificamos el valor del parámetro GatewayPorts que por defecto está a no:

GatewayPorts

Specifies whether remote hosts are allowed to connect to ports forwarded for the client. By default, sshd(8) binds remote port forwardings to the loopback address. This prevents other remote hosts from connecting to forwarded ports. GatewayPorts can be used to specify that sshd should allow remote port forwardings to bind to non-loopback addresses, thus allowing other hosts to connect. The argument may be “no” to force remote port forwardings to be available to the local host only, “yes” to force remote port forwardings to bind to the wildcard address, or “clientspecified” to allow the client to select the address to which the forwarding is bound. The default is “no”.

Si no tenemos la posibilidad de modificar la configuración del servidor, no podremos usar este tipo de reenvío de puertos. Al menos no de forma sencilla, ya que, si no hay otros impedimentos, un usuario puede abrir un puerto (>1024) para escuchar conexiones externas y reenviar dicha petición a localhost:7900. Esto se podría hacer, por ejemplo, con netcat (Debian #310431: sshd_config should warn about GatewayPorts workaround.)

De modo que en el /etc/ssh/sshd_config añadiremos:

GatewayPorts clientspecified

tras lo cual tendremos que releer la configuración con “sudo /etc/init.d/ssh reload” (Debian y Ubuntu).

Comprobamos que interno1 está escuchando peticiones de todas las IPs:

interno1 $ netstat -ltn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State      
...    
tcp        0      0 0.0.0.0:7900            0.0.0.0:*               LISTEN
...

Y ya podremos usar el servicio de VNC desde interno2:

interno2 $ vncviewer interno1::7900

Escenario 5: Usar en externo1 un servicio TCP ofrecido por interno2 (Local port forwarding / bind_address=localhost / host=interno2)

Túneles SSH: Escenario 5

En este escenario usaremos el siguiente comando:

externo1 $ ssh -L 7900:interno2:5900 usuario@interno1

Y accederemos al servicio ejecutando en externo1 el comando:

externo1 $ vncviewer localhost::7900

Escenario 6: Usar en interno1 un servicio TCP ofrecido por externo2 (Remote port forwarding / bind_address=localhost / host=externo2)

Túneles SSH: Escenario 6

En este escenario usaremos el siguiente comando:

externo1 $ ssh -R 7900:externo2:5900 usuario@interno1

Y accederemos al servicio ejecutando en interno1 el comando:

interno1 $ vncviewer localhost::7900

Escenario 7: Usar en externo2 un servicio TCP ofrecido por interno2 (Local port forwarding / bind_address=0.0.0.0 / host=interno2)

Túneles SSH: Escenario 7

En este escenario usaremos el siguiente comando:

externo1 $ ssh -L 0.0.0.0:7900:interno2:5900 usuario@interno1

o alternativamente:

externo1 $ ssh -g -L 7900:interno2:5900 usuario@interno1

Y accederemos al servicio ejecutando en externo2 el comando:

externo2 $ vncviewer externo1::7900

Escenario 8: Usar en interno2 un servicio TCP ofrecido por externo2 (Remote port forwarding / bind_address=0.0.0.0 / host=externo2)

Túneles SSH: Escenario 8

En este escenario usaremos el siguiente comando:

externo1 $ ssh -R 0.0.0.0:7900:externo2:5900 usuario@interno1

El servidor SSH tiene que estar configurado con “GatewayPorts clientspecified“, como hemos visto en el escenario 4.

Y accederemos al servicio ejecutando en interno2 el comando:

interno2 $ vncviewer interno1::7900

Actualización 26/10/2010:

Si queremos crear muchos túneles a la vez, puede ser cómodo usar un fichero de configuración en lugar de componer un comando larguísimo. Imaginemos que nuestro único punto de entrada a una red es por SSH y necesitamos crearnos túneles para acceder a los diferentes servidores de la red por SSH, VNC o Remote Desktop. Podríamos componer un fichero como el siguiente con todas las redirecciones que vamos a necesitar (en relación al servidor de SOCKS que se menciona: Reenvío dinámico de puertos / montar un servidor SOCKS con SSH):

# Servidor SOCKS
DynamicForward 1080

# Redirecciones SSH
LocalForward 2221 servidorlinux1:22
LocalForward 2222 servidorlinux2:22
LocalForward 2223 172.16.23.45:22
LocalForward 2224 172.16.23.48:22

# Redirecciones RDP para sistemas Windows
LocalForward 3391 servidorwindows1:3389
LocalForward 3392 servidorwindows2:3389

# Redirecciones VNC para sistemas con "vncserver"
LocalForward 5902 servidorlinux1:5901
LocalForward 5903 172.16.23.45:5901

y sólo necesitamos ejecutar esto para crear todas las redirecciones:

externo1 $ ssh -F $HOME/redirecciones usuario@interno1

:wq

Entradas relacionadas

20 Comentarios a “Creando túneles TCP/IP (port forwarding) con SSH: Los 8 escenarios posibles usando OpenSSH”

  • abaca dice:

    ¡qué chulo! con dibujillos y todo. Hace unas semanas tuve que jugar un poco con los túneles, y toda estas pistas me hubieran venido muy bien. Bueno, como no tenía esta chuleta, tuve que pensarlo yo mismo, así que, lo entendí ;-)

  • alidhaey dice:

    Buenisimo resumen. Una cosa ¿La opción -D no la incluyes?

  • dekanato dice:

    Gran post, todo muy bien explicado!

  • @abaca, @dekanato ¡Muchas gracias! :P .

    @alidhaey Gracias. Sin no me equivoco, te refieres a la capacidad de OpenSSH de actuar como servidor de SOCKS. Había considerado comentarlo, pero al final me he decantado por que esta entrada fuera una referencia rápida de los 8 escenarios posibles para el reenvío de puertos estático. Lo de la -D me lo guardo como posible tema para el futuro. ¡Gracias por la sugerencia!

  • trinux dice:

    ¡¡Que grande eres!! Muy buen artículo como siempre. Y bueno, el dibujo de super coco se sale ;)

  • vierito5 dice:

    Muy buen post. Sobretodo la claridad de la explicación.

  • Aurelio dice:

    ¡Fantastico post! Gracias.

  • lemd dice:

    Yo hubiera añadido algo sobre dynamic port forwarding, por ejemplo para salir por externo1 a internet desde interno1 (muy útil cuando estás en entornos con Wifi sin cifrar):

    interno1$ ssh -D 7070 externo1

    ahora puedes configurar tus aplicaciones para que salgan por localhost:7070 usando SOCKS

    Te ha quedado un post redondo (con dibujos y todo!). Enhorabuena! :-)

  • RuBiCK dice:

    Super Coco!

    Te ha salido un super-artículo :) En muchas empresas hay escenarios como los que comentan, sin duda será muy útil.

  • @vierito5, @Aurelio, @RuBICK ¡Gracias!

    @lemd ¡Gracias! Como le decía a alidhaey, me lo apunto para una posible futura entrada.

  • taromaru dice:

    Cuando consideres comentar la opcion proxy SOCKS, me imagino que un escenario de ejemplo será la navegación segura desde redes consideradas anti-higiénicas. Asumo que estarás al tanto, pero me gustaría asegurarme de que no se te escape la opción network.proxy.socks_remote_dns en firefox/iceweasel para tunelizar también las consultas DNS :)

    Como siempre, un excelente trabajo Super Coco!

  • Pablo dice:

    Buen artículo. Muy bien presentado y resumido. ¡Gracias!

  • jgpallero dice:

    Muy buenas,
    Sé que te lo dicen muchas veces, pero creo que no está de más repetirlo: tu blog es impresionante, lo mejor que se puede encontrar uno en español sobre temas técnicos serios. Una pregunta/sugerencia: ¿existe la posibilidad de generar un pdf con las entradas del blog? Uno siempre está tentado de guardar las entradas que le interesan, pero no deja de ser un engorro andar con los marcadores en el navegador o almacenar la página por otros medios.

    • Muchas gracias. No por mucho que lo repitan se lo acaba de creer uno ;-) .

      Respecto a lo de pasar entradas a PDF, sí que hay una forma sencilla de hacerlo. Como el blog tiene una hoja de estilos para imprimir (Hojas de estilo CSS para imprimir), puedes imprimir la página a PDF y ya está. Te quedará la página muy limpia y sin barras laterales ni comentarios ni nada. Pruébalo y ya me dirás si te gusta. El resultado sería el mismo que ves en la vista previa de impresión.

      En Linux hay varias formas de tener impresoras virtuales a PDF y, de hecho, varias distribuciones lo llevan por defecto. En Windows también hay diferentes programas con licencias más o menos libres.

  • jgpallero dice:

    Hola,
    Gracias por tu respuesta. Imprimiendo la página a PDF queda bastante bien.

Trackbacks y pingbacks:

Tema LHYLE09, creado por Vicente Navarro