如何通过一条命令从机器B SSH到机器A?

我想访问一台电脑,比如说在我的大学网络中的机器A。然而,这台电脑只能通过大学的内部网络访问,所以我不能直接从家里使用SSH连接到这台电脑。
现在我是这样做的:
  1. 登录到另一台大学电脑,比如说机器B

    (这台机器B可以通过SSH从我的家用电脑访问)

  2. 使用B上的SSH连接到A。

有没有更快的方法?只使用一个SSH命令。

相关:通过多个主机进行SSH - Trevor Boyd Smith
8个回答

在SSH配置中使用ProxyCommand

在您的主目录中创建一个SSH配置文件(除非您想要将其应用于整个系统),~/.ssh/config

Host unibroker          # Machine B definition (the broker)
Hostname 12.34.45.56    # Change this IP address to the address of the broker
User myusername         # Change this default user accordingly 
                        # (`user@unibroker` can overwrite it)

Host internalmachine    # Machine A definition (the target host)
ProxyCommand ssh -q unibroker nc hostname.or.IP.address.internal.machine 22

现在你可以直接使用以下方式联系到A机器
ssh user@internalmachine

请注意,现在您有一个单独的SSH主机目标名称,您也可以在其他应用程序中使用它。例如:
  • 使用SCP复制文件。

    scp somefile user@internalmachine:~/
    
  • 在您的GUI应用程序中:

    使用sftp://user@internalmachine/作为浏览机器上的位置。

    KDE-based (Dolphin):使用fish://user@internalmachine/

注意事项

hostname.or.IP.address.internal.machine和端口(22)更改为您想要从unibroker机器访问的机器。

根据unibroker主机上的netcat版本,可能需要省略-q0选项。关于身份验证;基本上是从您的工作站设置了两个SSH连接。这意味着unibroker主机和internalmachine主机都经过一个接一个的验证/身份验证(对于密钥对/密码和主机密钥验证)。

解释

这种使用ProxyCommand和'netcat'的方法只是其中一种方式。我喜欢这样做,因为我的SSH客户端直接与目标机器通信,这样我就可以从客户端验证主机密钥,并且我可以在不在代理服务器上使用另一个密钥的情况下使用我的公钥身份验证。

每个Host定义了一个新主机部分的开始。Hostname是该主机的目标主机名或IP地址。User是您在ssh user@hostname中提供的用户部分。

ProxyCommand将被用作到目标机器的管道。通过使用SSH连接到第一台机器,并直接在那里设置一个简单的'netcat' (nc)到目标机器,这基本上只是一个从代理人到内部机器的明文转发。 -q选项是为了消除任何输出(只是个人偏好)。

确保在代理人上安装了netcat(通常在Ubuntu上默认可用)- 可以选择安装netcat-openbsd Install netcat-openbsd或者netcat-traditional Install netcat-traditional

请注意,在这里您仍然使用了两次加密的SSH。虽然netcat通道是明文的,但您PC上的SSH客户端将与最终目标机器建立另一个加密通道。


4我不得不移除-q0选项,因为我使用的机器不支持它。除此之外,一切都很顺利。这是一个非常好的提示。非常感谢你。 :) - Gerry
我遇到了一个问题,你的答案在终端中对我很有效,但是我无法在图形界面的SFTP中执行它,它显示:未处理的错误消息,登录超时。 - Vikash Balasubramanian
@VikashB 嗯,这个应该是有效的。考虑创建一个新问题来处理你的具体情况。 - gertvdijk
我在这里做的是一个问题:http://askubuntu.com/questions/688567/connect-to-server-through-intermedieate-computer - Vikash Balasubramanian
2为了补充@gertvdijk的明智之言,关于ssh代理和跳转主机这个主题有一本很棒的维基书,可以作为一个宝贵的参考。 - Travis Clarke
非常感谢这个绝妙的提示。如果你在跳板机后面有很多主机,并且你有一个类似于Host *.some.domain.tld的条目,那么你几乎肯定希望在ProxyCommand中使用%h,而不是为每个特定的主机都添加一个条目,例如ProxyCommand ssh -q unibroker nc %h 22 - geronime

你可以使用命令行选项-J
ssh -J user@machineB user@machineA

man ssh中:

-J [user@]host[:port]
     Connect to the target host by first making a ssh connection to
     the jump host and then establishing a TCP forwarding to the
     ultimate destination from there.  Multiple jump hops may be
     specified separated by comma characters.  This is a shortcut to
     specify a ProxyJump configuration directive.

它是在OpenSSH 7.3版本中引入的(于2016年8月发布)。它可在Ubuntu 16.10及更高版本中使用。

4+1 是因为即使你需要为机器A指定密钥文件,这个方法仍然有效。 - Grief
2+1,如果您的所有主机都运行了足够新的OpenSSH版本,这是与我的ProxyCommand答案相比更好的版本。 - gertvdijk
在这种情况下,A和B机器上都应该安装OpenSSH服务器?我理解对了吗? - Mikhail
配置文件的等效项是"ProxyJump"。 - Amiratak88
在这种情况下,你如何指定关键文件?机器A的私钥需要放在机器B中吗?你如何指定两个SSH密钥? - rambi
@rambi 您的问题“机器A的私钥是否需要在机器B上?”不需要,在您的本地计算机上存储私钥即可。如果任何一个私钥位于非标准路径下,您可以通过编辑“~/.ssh/config”来配置其使用(请参考https://man7.org/linux/man-pages/man5/ssh_config.5.html中的“IdentityFile”)。 - Erik Sjölund
对于那些用户名中包含反斜杠的人(例如由Kerberos/GSSAPI/Active Directory管理的Linux用户,因此Unix用户名为例如Realm\mytargetuser),并且已经习惯在shell提示符上转义反斜杠(ssh Realm\\mytargetuser@target.intra)的人:由于某种原因,跳转代理用户名现在需要双重转义:ssh -J Realm\\\\myjumpuser@jump.intra Realm\\mytargetuser@target.intrassh -J 'Realm\\myjumpuser@jump.intra' Realm\\mytargetuser@target.intra - Abdull
这个选项比ProxyCommand稍微好一些,因为使用ProxyCommand需要在机器A和B上都有ssh密钥。对于我来说,我的一些内部机器是全新的机器,所以我需要输入密码。J标志对我的使用情况更有效。 - thefern

一次跳转

显而易见的替代方案是直接“一次跳转”到目标机器,而不是使用我在我的其他答案中提供的ProxyCommand方法:

ssh -t user@machineB ssh user@machineA

请注意第一个ssh命令中的-t选项。如果没有它,将会失败。
Pseudo-terminal will not be allocated because stdin is not a terminal.
ssh_askpass: exec(/usr/bin/ssh-askpass): No such file or directory
Permission denied, please try again.
[...]

它将强制分配一个真正的TTY

这样做的缺点是,现在所有的配置、验证和认证都在B机器上进行,出于安全原因,我不喜欢这种情况。我喜欢我的密钥对在自己的电脑上,并且从自己的电脑上进行身份验证和验证最终目标机器。此外,你只能使用SSH的交互式shell,所以这不能处理其他工具,如SCP或使用你的GUI文件管理器。

基于以上所有原因,我强烈推荐ProxyCommand方法,但对于快速连接,这个方法也可以。


为什么不将“一次性”和永久解决方案合并为一个答案呢? - demure
8@demure 这就是StackExchange网站的工作方式...请参考:回答同一个问题两次的官方礼仪是什么?中提到:“最好是发布两个不同的答案,而不是将它们放在一个答案中。”我认为这并不是同样的解决方案。在我看来,永久性或临时性并不是使这种方法与众不同的原因。 - gertvdijk
其他指南说要使用-A开关,但指出这会带来安全隐患。你知道转发身份验证代理连接是什么意思吗? - Diagon
@gertvdijk 超级忍者来了!你有两个最佳解决方案。我以前从未见过这样的情况。超酷!比起创建一个包含N个解决方案的超长解决方案要好得多(通常我都是这样看到的)。 - Trevor Boyd Smith
1这比设置一个会干扰其他SSH连接的配置要方便得多。 - Nikhil Sahu
在这种情况下,A和B机器都应该安装OpenSSH服务器?我理解得对吗? - Mikhail
@Mikhail 另外,机器B应该同时具备SSH服务器和客户端功能。而机器A只需要作为服务器使用。 - gertvdijk

尝试使用
Host <visible hostname alias>
        Controlmaster auto
        User <user>
        hostname <visible hostname>
        port <port>
        IdentityFile ~/.ssh/<id file>

Host <private LAN hostname alias>
     ProxyCommand ssh -q -W <private LAN hostname>:<private LAN port> <visible hostname alias>

在您的~/.ssh/config文件中,只需使用仅保存在您的计算机上的密钥,一次完成所有操作。

2这比将netcat引入混合环境更加干净。而且,B机器上也不需要存在私有SSH密钥。 - danemacmillan
我必须在这里包括 UserHost <私有局域网主机名别名> 下才能让它工作。 - Michael

machineA 是最终目标。 machineB 是中间代理。

我们希望通过ssh轻松地实现 home -- machineB -- machineA

OpenSSH版本7.3(于2016年8月发布)或更高版本中,您可以选择以下方法之一:

.ssh/config 方法:

将以下行添加到您的.ssh/config文件中:

Host machineB
    Hostname 12.12.12.12           # IP of machineB (w.r.t. home)
    User usernameB                 # username on machineB

Host machineA
    Hostname 34.34.34.34           # IP of machineA (w.r.t. machineB)
    ProxyJump usernameB@machineB   # PROXY MAGIC!
    User usernameA                 # username on machineA    

然后输入ssh machineA。也与scp兼容。 您可以通过https://unix.stackexchange.com/a/494485来增强上述配置,以强制使用特定的密钥。

-J标志选项:

只需输入ssh -J usernameB@machineB usernameA@machineA

最后说明

这个答案结合了gertvdijk和Erik Sjölund的答案,并参考了https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Proxies_and_Jump_Hosts#cite_note-ProxyJump-1中的指南。


这是一个非常有帮助的建议。在折腾了几个小时后,我找到了这个笔记,并确认它完全按照文档所述运行。要通过MachineA连接到MachineB,从远程的machineC上执行以下操作:
例如:[xuser@machineC ~] ssh -t MachineA ssh MachineB
"-t"是关键,如果没有它,ssh会失败。 你会被提示两次,首先是在MachineA上输入密码,然后是第二次在MachineB上输入密码。 另外,请注意,这假设你在这三台机器上都定义了用户"xuser"。如果没有的话,只需使用ssh语法:"yuser@MachineA ..."。还要注意,如果需要的话,你可以使用点分十进制的原始IP地址。这在通过私有局域网连接时很有用,该局域网使用的IP地址不对外公开,即不在你的本地主机文件或任何DNS中。要从MachineB获取文件到远程machineC,你可以先从MachineB到MachineA,然后再从MachineA到远程machineC进行scp操作。(例如,远程machineC可以ping通MachineA,但无法ping通MachineB。)注意:我在Fedora和WindowsXP上进行了测试,MachineA是运行ICS(Internet Connection Sharing)的XP机器,而MachineB和远程machineC是Fedora-Linux机器。这个建议解决了一个关键问题,即限制、监控对我远程站点局域网的远程访问。此外,当你从MachineB“退出”时,你应该会看到两条“Connection to xxx.xxx.xxx.xxx closed.”的消息。

如果您设置并配置了公钥身份验证,那么您就不需要担心输入密码的问题。但是-t仍然是必需的。 - Felipe Alvarez
@FelipeAlvarez 如果你不相信机器B可以无密码登录到A,那你还需要担心输入第二个密码! - Michael

你是说你需要几个跳线吗:)
最近,我遇到了一个问题,涉及到跳线1、跳线2和我的最终机器。至于我的解决方案,
本地脚本:
#!/bin/bash
sshpass -p jumper1Passwd ssh -t jumper1@jumper1IP ./Y00.sh

然后在第一个跳线(也就是我的路由器)上,我放置了一个名为Y00.sh的脚本。
ssh -tt jumper2@jumper2 -i ~/.ssh/id_rsa sshpass -p finalPassWord ssh -tt final@final

你可以用你的IP和密码替换这些,祝你好运!

ProxyCommand是一种干净的解决方案,适用于允许在两个系统中进行shell访问的情况。我们希望通过代理(B)为远程用户提供对内部机器(A)的访问权限,但又不允许用户对B进行shell访问以提高安全性。以下方法可实现此目的:

替换登录shell

使用chsh命令,将代理服务器上的extuser的登录shell替换为以下脚本(存储在文件中):

#!/bin/sh   # this is essential to avoid Exec format error
ssh internaluser@A

如果在extuser@B的远程用户和internaluser@A的extuser@B中没有设置密码登录,那么执行以下命令将直接将远程用户带到A。
ssh extuser@B

提示:在切换到自定义登录 shell 之前,在 extuser@B 中创建必要的无密码登录 authorized_keys 设置。更改后,由于此帐户无法通过 shell 访问,只有 sudoer@B 可以直接编辑文件 authorized_keys 进行更改。
sudo vi ~extuser/.ssh/authorized_keys
sudo touch ~extuser/.hushlogin

最后一行是为了抑制B的登录横幅显示,以便远程用户可以透明地访问A。

非常有趣的方法。然而,这种方法的主要缺点有:1) 仅限于标准SSH使用(不支持SFTP/SCP)。2) 用户无法选择除单一目标主机外的其他目标主机(因为在登录shell中硬编码)。3) 无法从工作站验证到最终目标主机的主机密钥(因为使用了代理SSH二进制文件)。4) 用户访问最终目标主机的私钥存储在代理上,而不是与用户一起。这允许管理员冒充用户(通常是不可能的)。 - gertvdijk
所有的观点都是正确的,感谢你的详细解释。然而,主要目的是为了让一个可信任的用户在不登录B的情况下获得对A的SSH访问权限。由于只有管理员可以通过sudo在B上添加可信任用户的公钥(无需登录),这已经意味着该用户拥有(管理员级别的)extuser@B的私钥访问权限(而非外部可信任用户的私钥),并且已经在第一时间进行了设置! - Sunthar