我希望能够在不同的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

我相信这是因为应该使用sendmsg以某种方式发送fd,但是fd并非源自子进程。我想使它尽可能简单,并且感觉可行。

最佳答案

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

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

如果您真的想使用SSH,那么我强烈建议您学习有关保护SSH服务器的信息。您已声明设备跨网络边界(NAT),并且客户端系统不 protected 。我不会阻止您用脚射击自己,但是在您陈述的情况下,如此壮观地拍摄非常容易。如果您处于工作环境中,则应与系统管理员联系,讨论防火墙规则,堡垒主机等类似内容。

是的,您可以按照您所说的去做。我强烈建议您谨慎。我强烈建议,我不会建议任何适合的方法。我将建议一个具有相同概念但更多身份验证的变体。

首先,您已经有效地设置了自己的SSH退回服务器,但是没有任何与SSH服务器兼容的常用工具。因此,这是我要解决的第一件事:使用SSHt服务器软件通过使用ssh客户端软件而不是socat来启动设备的连接,从而使用SSH服务器软件对传入的隧道请求进行身份验证。 ssh已经具有沿双向创建隧道的大量功能,并且您将身份验证与之 bundle 在一起(使用socat时,没有身份验证)。设备应该能够使用加密密钥进行身份验证(ssh称为这些身份)。您需要从设备手动连接一次,以验证和授权远程加密密钥指纹。您还需要将公共(public)密钥文件(而不是私有(private)密钥文件)复制到客户端计算机,并将其添加到您的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检查脚本。该脚本的目的是:
  • 在一个循环中,让您的设备(重新)连接到客户端以维护隧道。
  • 如果连接断开或失败,则每30秒重新连接一次。
  • 使用以下参数运行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,例如,您希望设备连接到的客户端计算机。
  • 永远睡眠
  • 如果由于套接字过期而导致连接失败,请通过以下方法解决此问题:
  • 分别登录
  • 删除(过时的)套接字
  • 打印今天的日期,指示何时删除
  • 再次循环

  • 有一个选项旨在使此错误处理变得多余:StreamLocalBindUnlink。但是,该选项无法正常工作,并且已打开bug多年。我想那是因为确实没有多少人使用ssh来转发unix域套接字。这很烦人,但并不难解决。
    使用Unix域套接字应将连接性限制为可以访问套接字文件的任何人(如果将其放置在root目录中并且该目录具有正确的权限,则该文件只能是您和${HOME}/.ssh)。我不知道这对您的情况是否重要。

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

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

    然后,可以从客户端反向连接:
    ssh -o ProxyCommand='socat - unix-connect:/home/WavesAtParticles/remotehost.sock' -l WavesAtParticles .
    

    在此调用中,您将启动ssh。然后,它将使用socat设置proxycommand。它将使用其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并不是一个好主意。

    关于linux - 如何在现有的TCP连接上运行ssh,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/57663192/

    10-11 22:04
    查看更多