【问题标题】:How to run ssh over an existing TCP connection如何通过现有 TCP 连接运行 ssh
【发布时间】:2019-08-29 09:11:10
【问题描述】:

我希望能够在不同的 NAT 后面一次通过 SSH 连接到多个 Linux 设备。我无法配置他们所在的网络。但是,我无法让 ssh 通过现有连接。

我可以完全控制我的客户端和设备。到目前为止的过程如下: 在我的客户端上,我首先运行

socat TCP-LISTEN:5001,pktinfo,fork EXEC:./create_socket.sh,fdin=3,fdout=4,nofork

./create_socket.sh的内容:

ssh -N -M -S "~/sockets/${SOCAT_PEERADDR}" -o "ProxyCommand=socat - FD:3!!FD:4" "root@${SOCAT_PEERADDR}"

在设备上,我正在运行

socat TCP:my_host:4321 TCP:localhost:22

但是,我认为FD:3!!FD:4 没有进出任何内容,因为 ProxyCommand 是一个子进程。我还尝试设置fdin=3,fdout=3 并将./create_socket.sh 更改为:

ssh -N -M -S "~/sockets/${SOCAT_PEERADDR}" -o "ProxyUseFdpass=yes" -o "ProxyCommand=echo 3" "root@${host}"

这会打印一个错误:

mm_receive_fd: no message header
proxy dialer did not pass back a connection

我相信这是因为 fd 应该使用sendmsg 以某种方式发送,但 fd 无论如何都不是源自子进程。我想让它尽可能简单,感觉接近可行。

【问题讨论】:

  • 如果我对您的理解正确,您希望从您的设备(在 NAT 之后)启动出站 TCP 连接。您希望工作站(“客户端”)接收连接。我假设您的客户端在 NAT 之后,在 5001 端口上打了一个洞(根据您的 socat 调用猜测)。但是您希望 SSH 连接反向:启动 TCP 连接的机器应该是 SSH 服务器,而接收 TCP 连接的机器是 SSH 客户端。 TCP连接的接收端应该是客户端吧?您想跨越清晰的网络边界吗?
  • 我的客户端不在 NAT 或防火墙之后,只有设备在。这就是为什么我想从设备启动 TCP 连接,然后通过该连接 ssh 回到设备。

标签: linux ssh


【解决方案1】:

您想颠覆客户端/服务器模型,并创建一个通用服务器来按需生成客户端并响应来自网络边界的传入未经身份验证的 TCP 连接 ,然后告诉新生成的客户端使用那个未经身份验证的 TCP 会话。 我认为这可能有您没有想到的安全考虑。如果恶意人员向您的计算机发送垃圾邮件连接,您的计算机将生成大量 SSH 实例进行连接返回,这些进程在进行身份验证时会占用大量本地系统资源。您实际上是在尝试将 SSH 设置为跨网络边界自动连接到不受信任(未经验证)的远程启动的机器。我无法强调这对您的客户端计算机有多么危险。使用错误的选项可能会暴露您拥有的任何凭据,甚至可能让恶意人员完全访问您的计算机。

还值得注意的是,您要求做的场景,在多个设备之间建立隧道以跨不受信任的网络边界多路复用其他连接,正是 VPN 软件的目的。是的,SSH 可以建立隧道。 VPN 软件可以更好地建立隧道。这个概念是您将在您的客户端计算机上运行一个 VPN 服务器。 VPN 服务器将创建一个新的(虚拟)网络接口,该接口代表您的设备。这些设备将连接到 VPN 服务器并被分配一个 IP 地址。然后,从客户端机器上,您只需启动 SSH 到设备的 VPN 地址,它将通过虚拟网络接口路由并到达设备并由其 SSH 守护程序服务器处理。然后,您无需使用socat 或SSH 选项进行端口转发。您将获得围绕 VPN 存在的所有工具和教程。 我强烈建议您查看 VPN 软件

如果您真的想使用 SSH,那么我强烈建议您了解如何保护 SSH 服务器。您已声明设备跨越网络边界 (NAT),并且您的客户端系统不受保护。我不会阻止你朝自己的脚开枪,但在你所说的情况下,非常容易做到这一点。如果你在工作环境中,你应该和你的系统管理员讨论防火墙规则、堡垒主机之类的东西。

是的,你可以按照你所说的去做。我强烈建议谨慎。我强烈建议它,我不会建议任何可以与如上所述一起使用的东西。我将建议一个具有相同概念但更多身份验证的变体。

首先,您已经有效地设置了自己的 SSH 退回服务器,但没有任何与 SSH 服务器兼容的通用工具。所以这是我要解决的第一件事:使用 SSH 服务器软件来验证传入的隧道请求,方法是使用 ssh 客户端软件从设备而不是 socat 启动连接。 ssh 已经有很多功能可以在两个 方向上创建隧道 你会获得与它捆绑的身份验证(socat,没有身份验证)。设备应该能够使用加密密钥进行身份验证(ssh 调用这些身份)。您需要从设备手动连接一次,以验证和授权远程加密密钥指纹。您还需要将公钥文件(不是私钥文件)复制到客户端计算机并将其添加到您的 authorized_keys 文件中。如果需要,您可以单独寻求帮助。

第二个问题是您似乎在使用 fd3 和 fd4。我不知道你为什么这样做。如果有的话,您应该使用 fd0 和 fd1,因为它们分别是 stdinstdout。但是,如果您使用socat 来启动连接,您甚至不需要这样做。只需使用-,其中stdinstdout 是指。它应该与-o ProxyCommand 完全兼容,无需指定任何文件描述符。这个答案的末尾有一个例子。

设备端的调用可能如下所示(将其放入脚本文件):

IDENTITY=/home/WavesAtParticles/.ssh/tunnel.id_rsa # on device
REMOTE_SOCKET=/home/WavesAtParticles/.ssh/$(hostname).sock # on client
REMOTEUSER=WavesAtParticles # on client
REMOTEHOST=remotehost # client hostname or IP address accessible from device
while true
do
  echo "$(date -Is) connecting"
  #
  # Set up your SSH tunnel. Check stderr for known issues.
  ssh \
    -i "${IDENTITY}" \
    -R "${REMOTE_SOCKET}:127.0.0.1:22" \
    -o ExitOnForwardFailure=yes \
    -o PasswordAuthentication=no \
    -o IdentitiesOnly=yes \
    -l "${REMOTEUSER}" \
    "${REMOTEHOST}" \
      "sleep inf" \
  2> >(
    read -r line
    if echo "${line}" | grep -q "Error: remote port forwarding failed"
    then
      ssh \
        -i "${IDENTITY}" \
        -o PasswordAuthentication=no \
        -o IdentitiesOnly=yes \
        -l "${REMOTEUSER}" \
        "${REMOTEHOST}" \
          "rm ${REMOTE_SOCKET}" \
      2>/dev/null # convince me this is wrong
      echo "$(date -Is) removed stale socket"
    fi
    #
    # Re-print stderr to the terminal
    >&2 echo "${line}" # the stderr line we checked
    >&2 cat - # and any unused stderr messages
  )

  echo "disconnected"
  sleep 30
done

请记住,就 shell 脚本而言,复制和粘贴是不好的。至少,我建议您阅读man sshman ssh_config,并对照shellcheck.net 检查脚本。该脚本的意图是:

  1. 在一个循环中,让您的设备(重新)连接到您的客户端以维护您的隧道。
  2. 如果连接断开或失败,则每 30 秒重新连接一次。
  3. 使用以下参数运行ssh
    • -i "${IDENTITY}":指定用于身份验证的私钥。
    • -R "${REMOTE_SOCKET}:127.0.0.1:22":指定一个连接请求转发器,它接受远程端的连接 /home/WavesAtParticles/$(hostname).sock,然后通过连接到将它们转发到本地端 127.0.0.1:22
    • -o ExitOnForwardFailure=yes:如果远程端无法设置连接转发器,那么本地端应该发出一个错误并死掉(我们在子shell中检查这个错误)。
    • -o PasswordAuthentication=no:不要回退到密码请求,特别是因为本地用户不在这里输入它
    • -o IdentitiesOnly=yes:不要使用任何默认身份,也不要使用任何本地代理提供的任何身份。仅使用-i 指定的那一种。
    • -l "${REMOTEUSER}":以指定用户登录。
    • remotehost,例如您希望设备连接到的客户端计算机。
  4. 永远长眠
  5. 如果由于套接字陈旧而导致连接失败,请通过以下方式解决该问题:

    • 单独登录
    • 删除(陈旧的)套接字
    • 打印今天的日期,指明删除的时间
    • 再次循环

    有一个选项旨在使这种错误处理变得多余:StreamLocalBindUnlink。但是,该选项无法正常工作,并且 bug 已打开多年。我想那是因为确实没有多少人使用 ssh 通过 unix 域套接字进行转发。这很烦人,但解决起来并不难。

使用 unix 域套接字应该限制任何可以访问套接字文件的人的连接(如果它放在您的 ${HOME}/.ssh 目录中并且该目录具有正确的权限,则应该只有您和 root)。我不知道这对你的案子是否重要。

另一方面,如果您愿意为每个设备在127.0.0.1 上打开一个 TCP 端口,您也可以大大简化此操作。但是同一系统上的任何其他用户也可以连接。您应该特别地监听127.0.0.1,然后它只会接受来自同一主机的连接,以防止外部机器到达转发端口。例如,您可以将 ${REMOTE_SOCKET} 变量更改为 127.0.0.1:4567 以侦听端口 4567 并且只接受本地连接。因此,您将失去 named socket 功能并允许客户端计算机上的任何其他用户连接到您的设备,但获得一个更简单的隧道脚本(因为您可以删除有关将 stderr 解析为删除陈旧的套接字文件)。

只要您的设备在线(可以访问工作站的传入端口)并且正在运行该脚本,并且身份验证有效,那么隧道也应该在线或即将上线。但是,在网络连接丢失(和恢复)后需要一些时间才能恢复。您可以使用ConnectTimeoutTCPKeepAliveServerAliveInterval 选项以及循环的sleep 30 部分对其进行调整。您可以在 tmux 会话中运行它,即使您没有运行登录会话,也可以继续运行。您还可以在设备上将其作为系统服务运行,即使在从电源故障中恢复后也可以使其联机。

然后从您的客户端,您可以反向连接:

ssh -o ProxyCommand='socat - unix-connect:/home/WavesAtParticles/remotehost.sock' -l WavesAtParticles .

在此调用中,您将从 ssh 开始。然后它将使用socat 设置代理命令。它将获取其 stdin/stdout 并通过连接的 AF_UNIX 套接字在提供的路径上中继它。您需要更新您期望的远程主机的路径。但是根本不需要指定文件描述符。 如果ssh 抱怨:

2019/08/26 18:09:52 socat[29914] E connect(5, AF=1 "/home/WavesAtParticles/remotehost.sock", 7): Connection refused
ssh_exchange_identification: Connection closed by remote host

那么隧道当前处于关闭状态,您应该调查remotehost 设备的连接性。

如果您将远程转发选项与 TCP 端口侦听而不是 unix 域套接字一起使用,则客户端通过隧道到远程调用变得更加容易:ssh -p 4567 WavesAtParticles@localhost

同样,您试图颠倒客户端/服务器模型,我认为这不是使用 SSH 的好主意。

【讨论】:

  • 澄清一下,我只是想创建一个 TCP 隧道,然后尝试通过隧道 ssh,让 ssh 处理身份验证。我不想使用 ssh -R 因为那样我必须在客户端上创建一个设备可以访问的用户,并且我希望他们没有特权。对于用户,设备可以通过客户端隧道,但我只希望客户端能够访问设备,而不是相反。有用的是,VPN 是 TCP 隧道。感谢您的评论!
  • 我明白你想要做什么。我告诉你这是不明智的。匿名连接不应该能够告诉您的计算机运行程序,尤其是可以授予他们访问权限以运行更多程序(例如 SSH)的程序。身份验证需要在您的 ssh 客户端进程启动之前进行,否则您会自找麻烦。
  • 假设会话中没有任何操作,通过 ssh 进入未知主机是否不安全?主机无法控制客户端,对吧?
  • 对于“通过 ssh 连接到未知主机是否不安全?”已有很多答案。我在几秒钟内找到了这个答案:security.stackexchange.com/q/38128/47800
猜你喜欢
  • 2013-02-19
  • 2011-04-28
  • 2017-10-20
  • 1970-01-01
  • 2018-07-27
  • 2012-02-03
  • 2011-08-04
  • 1970-01-01
  • 2020-10-10
相关资源
最近更新 更多