您想颠覆客户端/服务器模型,并创建一个通用服务器来按需生成客户端并响应来自网络边界的传入未经身份验证的 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,因为它们分别是 stdin 和 stdout。但是,如果您使用socat 来启动连接,您甚至不需要这样做。只需使用-,其中stdin 和stdout 是指。它应该与-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 ssh 和man 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 域套接字应该限制任何可以访问套接字文件的人的连接(如果它放在您的 ${HOME}/.ssh 目录中并且该目录具有正确的权限,则应该只有您和 root)。我不知道这对你的案子是否重要。
另一方面,如果您愿意为每个设备在127.0.0.1 上打开一个 TCP 端口,您也可以大大简化此操作。但是同一系统上的任何其他用户也可以连接。您应该特别地监听127.0.0.1,然后它只会接受来自同一主机的连接,以防止外部机器到达转发端口。例如,您可以将 ${REMOTE_SOCKET} 变量更改为 127.0.0.1:4567 以侦听端口 4567 并且只接受本地连接。因此,您将失去 named socket 功能并允许客户端计算机上的任何其他用户连接到您的设备,但获得一个更简单的隧道脚本(因为您可以删除有关将 stderr 解析为删除陈旧的套接字文件)。
只要您的设备在线(可以访问工作站的传入端口)并且正在运行该脚本,并且身份验证有效,那么隧道也应该在线或即将上线。但是,在网络连接丢失(和恢复)后需要一些时间才能恢复。您可以使用ConnectTimeout、TCPKeepAlive 和ServerAliveInterval 选项以及循环的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 的好主意。