aha*_*son 42 ssh reverse-proxy
我越来越喜欢我们开发环境中的 HTTP 反向代理,并发现基于 DNS 的虚拟主机反向代理非常有用。在防火墙上只打开一个端口(和标准端口)使管理变得更加容易。
我想找到类似于 SSH 连接的东西,但运气不佳。我不想简单地使用 SSH 隧道,因为这需要打开标准以外的端口范围。有什么东西可以做到这一点吗?
HAProxy 能做到吗?
kas*_*erd 22
在过去的 16 个月里,我一直在寻找解决这个问题的方法。但是每次我查看时,似乎不可能使用相关 RFC 中指定并由主要实现实现的 SSH 协议来做到这一点。
但是,如果您愿意使用稍微修改过的 SSH 客户端,并且愿意以设计时不完全预期的方式使用协议,那么就有可能实现。更多关于这个下面。
为什么不可能
客户端不会将主机名作为 SSH 协议的一部分发送。
它可能会将主机名作为 DNS 查找的一部分发送,但这可能会被缓存,并且从客户端通过解析器到权威服务器的路径可能不会跨越代理,即使它确实存在,也没有可靠的方式将特定的 DNS 查找与特定的 SSH 客户端。
SSH 协议本身也没什么特别的。您必须在没有看到客户端的 SSH 版本横幅的情况下选择服务器。您必须先向客户端发送横幅,然后才能向代理发送任何内容。来自服务器的横幅可能不同,您无法猜测使用哪一个是正确的。
即使此横幅未加密发送,您也无法修改它。该横幅的每一位都将在连接设置期间进行验证,因此您可能会导致连接失败。
我的结论很清楚,必须在客户端进行一些更改才能使这种连接正常工作。
大多数解决方法是将 SSH 流量封装在不同的协议中。还可以想象 SSH 协议本身的附加内容,其中客户端发送的版本横幅包含主机名。这可以与现有服务器保持兼容,因为当前横幅的一部分被指定为自由格式标识字段,尽管客户端通常会在发送自己的横幅之前等待服务器的版本横幅,但该协议确实允许客户端发送他们的横幅第一的。一些最新版本的 ssh 客户端(例如 Ubuntu 14.04 上的那个)确实会在不等待服务器横幅的情况下发送横幅。
我不知道有任何客户端已采取措施在此横幅中包含服务器的主机名。我送一个补丁到OpenSSH的邮件列表中添加这样的功能。但由于不想向任何窥探 SSH 流量的人透露主机名而被拒绝。由于秘密主机名从根本上与基于名称的代理的操作不兼容,因此不要指望很快就会看到 SSH 协议的官方 SNI 扩展。
一个真正的解决方案
最适合我的解决方案实际上是使用 IPv6。
使用 IPv6,我可以为每个服务器分配一个单独的 IP 地址,因此网关可以使用目标 IP 地址来找出将数据包发送到哪个服务器。SSH 客户端有时可能在网络上运行,在这些网络中,获取 IPv6 地址的唯一方法是使用 Teredo。众所周知,Teredo 不可靠,但仅当连接的本机 IPv6 端使用公共 Teredo 中继时才会如此。可以简单地在网关上放置一个 Teredo 中继,您将在其中运行代理。Miredo 可以在不到五分钟的时间内安装和配置为继电器。
解决方法
您可以使用跳转主机/堡垒主机。这种方法适用于您不想将单个服务器的 SSH 端口直接暴露给公共互联网的情况。它确实具有减少 SSH 所需的面向外部的 IP 地址数量的额外好处,这就是它在这种情况下可用的原因。出于安全原因,它是一种旨在添加另一层保护的解决方案,这一事实并不会阻止您在不需要增加的安全性时使用它。
ssh -o ProxyCommand='ssh -W %h:%p user1@bastion' user2@target
Run Code Online (Sandbox Code Playgroud)
如果真正的解决方案 (IPv6) 超出您的能力范围,则很难破解以使其工作
我将要描述的 hack 应该只用作绝对的最后手段。在您考虑使用这个 hack 之前,我强烈建议您为您希望通过 SSH 从外部访问的每个服务器获取一个 IPv6 地址。使用 IPv6 作为访问 SSH 服务器的主要方法,并且仅当您需要从仅 IPv4 的网络运行 SSH 客户端时才使用此技巧,而您对部署 IPv6 没有任何影响。
这个想法是客户端和服务器之间的流量需要是完全有效的 SSH 流量。但是代理只需要足够了解数据包流即可识别主机名。由于 SSH 没有定义发送主机名的方式,您可以考虑其他提供这种可能性的协议。
HTTP 和 HTTPS 都允许客户端在服务器发送任何数据之前发送主机名。现在的问题是,是否有可能构建一个字节流,它同时作为 SSH 流量以及 HTTP 和 HTTPS 有效。HTTPs 它几乎是一个非入门者,但 HTTP 是可能的(对于 HTTP 的足够自由的定义)。
SSH-2.0-OpenSSH_6.6.1 / HTTP/1.1
$:
Host: example.com
Run Code Online (Sandbox Code Playgroud)
对您来说,这看起来像 SSH 还是 HTTP?它是 SSH 并且完全符合 RFC(除了一些二进制字符被 SF 渲染弄乱了一点)。
SSH 版本字符串包括一个注释字段,在上面它的值为/ HTTP/1.1
。换行后 SSH 有一些二进制数据包数据。第一个数据包是MSG_SSH_IGNORE
客户端发送并被服务器忽略的消息。要忽略的有效负载是:
:
Host: example.com
Run Code Online (Sandbox Code Playgroud)
如果 HTTP 代理在它接受的内容方面足够自由,那么相同的字节序列将被解释为调用的 HTTP 方法SSH-2.0-OpenSSH_6.6.1
,并且忽略消息开头的二进制数据将被解释为 HTTP 标头名称。
代理既不会理解 HTTP 方法,也不会理解第一个标头。但它可以理解Host
标头,这是它找到后端所需的全部。
为了让它工作,代理的设计原则是它只需要理解足够的 HTTP 来找到后端,一旦找到后端,代理将简单地传递原始字节流并留下真正的终止由后端完成的 HTTP 连接。
对 HTTP 代理做出如此多的假设听起来似乎有些牵强。但是,如果您愿意安装一个旨在支持 SSH 的新软件,那么对 HTTP 代理的要求听起来还不错。
在我自己的情况下,我发现此方法适用于已安装的代理,无需更改代码、配置或其他方式。这是针对仅使用 HTTP 而没有考虑 SSH 编写的代理。
概念验证客户端和代理。(免责声明代理是由我运营的服务。一旦确认任何其他代理支持此用途,请随时更换链接。)
这个黑客的注意事项
街区里来了一个新孩子。SSH Piper 将根据预定义的用户名路由您的连接,该用户名应映射到内部主机。这是反向代理背景下的最佳解决方案。
我的第一次测试证实,SSH 和 SFTP 都可以工作。