Un túnel SSH a través de múltiples saltos

332

La tunelización de datos a través de SSH es bastante sencilla:

ssh -D9999 [email protected]

configura el puerto 9999 en tu localhostcomo un túnel para example.com, pero tengo una necesidad más específica:

  • Estoy trabajando localmente en localhost
  • host1 es accesible para localhost
  • host2 solo acepta conexiones de host1
  • Necesito crear un túnel de localhostahost2

Efectivamente, quiero crear un túnel SSH "multi-hop". ¿Cómo puedo hacer esto? Idealmente, me gustaría hacer esto sin necesidad de ser superusuario en ninguna de las máquinas.

Mala
fuente
2
¿Para qué lo usaste? Quiero usarlo para calcetines proxy. ¿Funcionará?
dientes
2
Sí, debería poder usar la conexión tunelizada como proxy SOCKS, a menos que host2niegue el reenvío
Mala
Estaba pensando en crear un contenedor sobre SSH que configuraría eso usando el uso múltiple de ProxyCommand.
Pavel Šimerda
@prongs ¿Has logrado usar esto para el proxy SOCKS (hace todos esos años)?
Drux

Respuestas:

324

Básicamente tienes tres posibilidades:

  1. Túnel de localhosta host1:

    ssh -L 9999:host2:1234 -N host1
    

    Como se señaló anteriormente, la conexión de host1a host2no se asegurará.

  2. Túnel de localhosta host1y desde host1a host2:

    ssh -L 9999:localhost:9999 host1 ssh -L 9999:localhost:1234 -N host2
    

    Esto abrirá un túnel de localhosta host1y otro túnel de host1a host2. Sin embargo el puerto 9999a host2:1234puede ser utilizado por cualquier persona en host1. Esto puede o no ser un problema.

  3. Túnel de localhosta host1y desde localhosta host2:

    ssh -L 9998:host2:22 -N host1
    ssh -L 9999:localhost:1234 -N -p 9998 localhost
    

    Esto abrirá un túnel desde localhosta host1través del cual el servicio SSH host2se puede utilizar. Luego se abre un segundo túnel desde localhosta host2través del primer túnel.

Normalmente, iría con la opción 1. Si la conexión de host1a host2necesita ser asegurada, vaya con la opción 2. La opción 3 es principalmente útil para acceder a un servicio al host2que solo se puede acceder desde host2sí mismo.

Mika Fischer
fuente
17
La opción 3 era lo que estaba buscando, ¡gracias!
Mala
1
Quiero navegar de esta manera. ¿Cuál es el mejor? Probé el primero pero no funcionó. Configuré un proxy de calcetines en mi navegador localhost: 1234 pero no tuve suerte. :( por favor ayuda ..
dientes
1
@prongs prueba opción 3
Mala
66
@Noli Si usa ssh-agent (que debería), puede reenviarlo a través de las conexiones usando la -Aopción ssh.
Mika Fischer
2
@musically_ut: eso es correcto, el segundo comando ssh está en segundo plano en host1, por lo que si desea volver a conectarse localmente con él, solo necesita volver a ejecutar la primera parte nuevamente. Si agrega -f, inmediatamente se creará un fondo local, por lo que se ssh -f -L 9999:localhost:9999 host1volverá a conectar si presionó ctrl-c en el primer caso. O agregue -fal comando doble ssh original cuando lo ejecute en segundo plano todo inmediatamente.
Mark Fisher
153

Hay una excelente respuesta que explica el uso de la ProxyCommanddirectiva de configuración para SSH :

Agregue esto a su ~/.ssh/config(ver man 5 ssh_configpara más detalles):

Host host2
  ProxyCommand ssh host1 -W %h:%p

Luego ssh host2, se canalizará automáticamente host1(también funciona con reenvío X11, etc.).

Esto también funciona para una clase completa de hosts, por ejemplo, identificados por dominio:

Host *.mycompany.com
  ProxyCommand ssh gateway.mycompany.com -W %h:%p

Actualizar

OpenSSH 7.3 presenta una ProxyJumpdirectiva, simplificando el primer ejemplo para

Host host2
  ProxyJump host1
kynan
fuente
3
¿Hay alguna manera de hacer esto condicionalmente? Solo quiero hacer esto a veces. Además, esto es específicamente para comandos, pero estoy buscando algo para todo el puerto 22 (ssh, sftp, etc.).
Stephane
1
@Stephane, ¿a qué te refieres específicamente con comandos ? Su configuración SSH es utilizada por cualquier cosa que use ssh, incluyendo git, sftpetc. afaik.
kynan
1
@Stephane No conozco una forma de habilitar esto condicionalmente (por ejemplo, solo cuando está fuera de la red del host de destino). Establezco esta opción para todos los hosts en cuestión en un bloque de configuración y luego (des) comentar la línea según sea necesario. No es perfecto, pero funciona.
kynan
2
@Stephane Seguro: ssh -F /path/to/altconfig. Tenga en cuenta que esto ignorará todo el sistema /etc/ssh/ssh_config.
kynan
19
Una manera fácil de hacer que la configuración sea "condicional" es definir dos hosts diferentes en .ssh / config, que tengan el mismo HostName. Conéctese a host2-tunnel cuando desee el túnel, y host2 cuando no lo desee.
Steve Bennett
25

OpenSSH v7.3 en adelante admite un -Jconmutador y una ProxyJumpopción, que permiten uno o más hosts de salto separados por comas, por lo tanto, simplemente puede hacer esto ahora:

ssh -J jumpuser1@jumphost1,jumpuser2@jumphost2,...,jumpuserN@jumphostN user@host
nikolay
fuente
ssh -J user1 @ host1 -YC4c arcfour, blowfish-cbc user2 @ host2 firefox -no-remote Esto acelerará la transferencia de firefox de host2 a localhost.
Jaur
21

Tenemos una puerta de enlace ssh en nuestra red privada. Si estoy afuera y quiero un shell remoto en una máquina dentro de la red privada, tendría que ingresar a la puerta de enlace y de allí a la máquina privada.

Para automatizar este procedimiento, utilizo el siguiente script:

#!/bin/bash
ssh -f -L some_port:private_machine:22 user@gateway "sleep 10" && ssh -p some_port private_user@localhost

Qué está pasando:

  1. Establezca un túnel para el protocolo ssh (puerto 22) a la máquina privada.
  2. Solo si esto tiene éxito, ssh en la máquina privada usando el túnel. (El operador && asegura esto).
  3. Después de cerrar la sesión privada ssh, quiero que el túnel ssh también se cierre. Esto se hace a través del truco "dormir 10". Por lo general, el primer comando ssh se cerraría después de 10 segundos, pero durante este tiempo, el segundo comando ssh habrá establecido una conexión utilizando el túnel. Como resultado, el primer comando ssh mantiene el túnel abierto hasta que se cumplan las siguientes dos condiciones: el sueño 10 ha finalizado y el túnel ya no se usa.
Bernhard Kausler
fuente
1
¡¡¡Muy inteligente!!! ¡QUIÉRALO!
Hendy Irawan
18

Después de leer lo anterior y pegar todo, he creado el siguiente script de Perl (guárdelo como mssh en / usr / bin y hágalo ejecutable):

#!/usr/bin/perl

$iport = 13021;
$first = 1;

foreach (@ARGV) {
  if (/^-/) {
    $args .= " $_";
  }
  elsif (/^((.+)@)?([^:]+):?(\d+)?$/) {
    $user = $1;
    $host = $3;
    $port = $4 || 22;
    if ($first) {
      $cmd = "ssh ${user}${host} -p $port -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no";
      $args = '';
      $first = 0;
    }
    else {
      $cmd .= " -L $iport:$host:$port";
      push @cmds, "$cmd -f sleep 10 $args";
      $cmd = "ssh ${user}localhost -p $iport -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no";
      $args = '';
      $iport ++;
    }
  }
}
push @cmds, "$cmd $args";

foreach (@cmds) {
  print "$_\n";
  system($_);
}

Uso:

Para acceder a HOSTC a través de HOSTA y HOSTB (mismo usuario):

mssh HOSTA HOSTB HOSTC

Para acceder a HOSTC a través de HOSTA y HOSTB y usar números de puerto SSH no predeterminados y diferentes usuarios:

mssh user1@HOSTA:1234 user2@HOSTB:1222 user3@HOSTC:78231

Para acceder a HOSTC a través de HOSTA y HOSTB y usar X-forwarding:

mssh HOSTA HOSTB HOSTC -X

Para acceder al puerto 8080 en HOSTC a través de HOSTA y HOSTB:

mssh HOSTA HOSTB -L8080:HOSTC:8080
Bob Muller
fuente
1
esto es increíble
Mala
1
En serio, no puedo agradecerles lo suficiente, este script me hace la vida más fácil a diario. Lo único que cambié fue agregar int (rand (1000)) a iport, para permitir que se ejecuten varias instancias al mismo tiempo. Definitivamente te debo una cerveza.
Mala
Esto funciona muy bien Una mejora adicional sería resolver HOSTB, HOSTC, etc. utilizando localhost's / etc / hosts y ~ / .ssh / config
Steve Bennett el
También secundo el comentario de Mala. Sin el puerto aleatorizado, si lo intentas mssh HOSTA HOSTD, terminarás en HOSTB (y tal vez no te des cuenta ...)
Steve Bennett
8

Esta respuesta es similar a Kynan, ya que implica el uso de ProxyCommand. Pero es más conveniente usar IMO.

Si tiene instalado netcat en sus máquinas de salto, puede agregar este fragmento a su ~ / .ssh / config:

Host *+*
    ProxyCommand ssh $(echo %h | sed 's/+[^+]*$//;s/\([^+%%]*\)%%\([^+]*\)$/\2 -l \1/;s/:/ -p /') nc $(echo %h | sed 's/^.*+//;/:/!s/$/ %p/;s/:/ /')

Entonces

ssh -D9999 host1+host2 -l username

Hará lo que le pediste.

Vine aquí buscando el lugar original donde leí este truco. Publicaré un enlace cuando lo encuentre.

silviot
fuente
2
Creo que este es el origen del truco: wiki.gentoo.org/wiki/SSH_jump_host
slm
5

Hice lo que creo que querías hacer con

ssh -D 9999 -J host1 host2

Se me solicitan ambas contraseñas, luego puedo usar localhost: 9999 para un proxy SOCKS para host2. Es lo más parecido al ejemplo que mostraste en primer lugar.

Cheryl
fuente
Esto funcionó perfectamente!
Arthur Silva
4
ssh -L 9999:host2:80 -R 9999:localhost:9999 host1

-L 9999: host2: 80

Significa enlazar a localhost: 9999 y cualquier paquete enviado a localhost: 9999 lo reenvía a host2: 80

-R 9999: localhost: 9999

Significa que cualquier paquete recibido por host1: 9999 lo reenvía a localhost: 9999

chinmaya
fuente
La respuesta más simple y brillante para hacer un túnel para que pueda acceder a la aplicación en host2 directamente desde localhost: 9999
hasta el
Después de esta respuesta, recibo un channel 3: open failed: administratively prohibited: open failed mensaje de error.
Franck Dernoncourt
2

debería poder utilizar el reenvío de puertos para acceder a un servicio host2desde localhost. Una buena guía se encuentra aquí . Extracto:

Hay dos tipos de reenvío de puertos: reenvío local y remoto. También se denominan túneles salientes y entrantes, respectivamente. El reenvío de puerto local reenvía el tráfico que llega a un puerto local a un puerto remoto especificado.

Por ejemplo, si emite el comando

ssh2 -L 1234:localhost:23 username@host

todo el tráfico que llega al puerto 1234 en el cliente se reenviará al puerto 23 en el servidor (host). Tenga en cuenta que localhost será resuelto por el servidor sshdserver después de que se establezca la conexión. En este caso, localhost se refiere al servidor (host) en sí.

El reenvío de puerto remoto hace lo contrario: reenvía el tráfico que llega a un puerto remoto a un puerto local específico.

Por ejemplo, si emite el comando

ssh2 -R 1234:localhost:23 username@host

todo el tráfico que llega al puerto 1234 en el servidor (host) se reenviará al puerto 23 en el cliente (localhost).

En su reparto, reemplace localhosten el ejemplo con host2y hostcon host1.

fideli
fuente
de acuerdo con ese artículo, la conexión solo se asegurará hasta la máquina central (host1). ¿Hay alguna manera de asegurarse de que todo se mantenga seguro?
Mala
Nunca he intentado esto, pero si host1 y host2 son servidores ssh, es posible que pueda configurar un túnel de host1 a host2, luego configure un túnel de localhost a host1 para el mismo servicio (obteniendo su local y remoto puertos a la derecha). No sé si eso es posible en un comando de localhost.
fideli
1

En esta respuesta voy a pasar por un ejemplo concreto. Solo necesita reemplazar los nombres de host, nombres de usuario y contraseñas de las computadoras por los suyos.

Planteamiento del problema

Supongamos que tenemos la siguiente topología de red:

our local computer <---> server 1 <---> server 2

En aras de la concreción, supongamos que tenemos los siguientes nombres de host, nombres de usuario y contraseñas de las computadoras:

LocalPC            <--->  hostname: mit.edu         <---> hec.edu
                          username: bob                   username: john 
                          password: dylan123              password: doe456

Objetivo: queremos configurar un proxy calcetines que escucha en el puerto 9991de LocalPCmodo que cada vez que una conexión en la LocalPCque se inicia desde el puerto 9991pasa por mit.eduentonces hec.edu.

Ejemplo de caso de uso: hec.edutiene un servidor HTTP al que solo se puede acceder en http://127.0.0.1:8001 , por razones de seguridad. Nos gustaría poder visitar http://127.0.0.1:8001 abriendo un navegador web en LocalPC.


Configuración

En LocalPC, agregue ~/.ssh/config:

Host HEC
    HostName hec.edu
    User john
    ProxyCommand ssh [email protected] -W %h:%p

Luego, en la terminal de LocalPC, ejecute:

ssh -D9991 HEC

Le pedirá la contraseña de bobon mit.edu(es decir, dylan123), luego le pedirá la contraseña de johnon hec.edu(es decir, doe456).

En ese momento, el proxy SOCKS ahora se está ejecutando en el puerto 9991de LocalPC.

Por ejemplo, si desea visitar una página web sobre el LocalPCuso del proxy SOCKS, puede hacerlo en Firefox:

ingrese la descripción de la imagen aquí

Algunas observaciones:

  • en ~/.ssh/config, HECes el nombre de la conexión: puede cambiarlo a lo que desee.
  • Le -D9991dice sshque configure un proxy SOCKS4 en el puerto 9991.
Franck Dernoncourt
fuente
0

Si puede utilizar SSH en ambas máquinas, eche un vistazo a la directiva ProxyCommand de ssh. Esto te permitirá pasar directamente de localhost a host2 (¡en un comando fácil si usas claves públicas!). Entonces puedes hacer lo que quieras con host2.

http://www.statusq.org/archives/2008/07/03/1916/


fuente
0

La opción 2 de la mejor respuesta podría usarse con diferentes usuarios de ssh que la actual aka: user @ host

    export local_host_port=30000
    export host1_user=xyz
    export host1=mac-host
    export host1_port=30000
    export host2=192.168.56.115
    export host2_user=ysg
    export host2_port=13306

    # Tunnel from localhost to host1 and from host1 to host2
    # you could chain those as well to host3 ... hostn
    ssh -tt -L $local_host_port:localhost:$host1_port $host1_user@$host1 \
    ssh -tt -L $host1_port:localhost:$host2_port $host2_user@$host2
Yordan Georgiev
fuente
0

En mi caso lo hice

localhost$ ssh -D 9999 host1
host1$ ssh -L 8890:localhost:8890 host2

donde host2:8890se ejecuta en un Jupyter Notebook.

Luego configuré Firefox para usarlo localhost:9999como host SOCKS.

Así que ahora tengo el portátil funcionando host2accesible por Firefox en localhost:8890mi máquina.

amarion
fuente
0

Las tres opciones mencionadas en la respuesta aceptada no me funcionaron en absoluto. Dado que no tengo mucho permiso sobre ambos hosts, y parece que nuestro equipo DevOps tiene reglas bastante estrictas cuando se trata de autenticación y están haciendo MFA. De alguna manera, los comandos anteriores no pueden funcionar bien con nuestra autenticación.

Sin embargo, el contexto es realmente similar a las respuestas anteriores: no puedo ingresar directamente al servidor de producción, y tengo que hacer 1 salto usando un servidor de salto.

Otra solución más: una ingenua

Terminé haciéndolo de una manera muy ingenua: en lugar de intentar ejecutar todos los comandos en mi computadora portátil, ejecuto los comandos en cada una de las máquinas , como se muestra a continuación:

  1. SSH en su servidor de salto, luego corra ssh -v -L 6969:localhost:2222 -N your-protected.dest.server. Si se le solicita que ingrese una contraseña, escríbala.
  2. Ahora en tu computadora portátil, corre ssh -v -L 6969:localhost:6969 -N your-jump-server.host.name. Esto reenviará cualquiera de sus solicitudes en el puerto 6969 de su computadora portátil al servidor de salto. Luego, por turno, como configuramos en nuestro paso anterior, el servidor de salto reenviará nuevamente las solicitudes del puerto 6969 al puerto 2222 en el servidor de destino protegido.

Debería ver el comando "se cuelga" allí después de imprimir un mensaje, ¡significa que están funcionando! Una excepción: no debería ver un mensaje de error como Could not request local forwarding., si ve eso, entonces todavía no funciona :(. Ahora puede intentar activar la solicitud en el puerto 6969 desde su computadora portátil y ver si funciona.

Con suerte, si eres alguien que falló en todos los métodos anteriores, tal vez puedas probar esto.

Shaung Cheng
fuente