我已经非常喜欢我们开发环境中的 HTTP 反向代理,并且发现基于 DNS 的虚拟主机反向代理非常有用。防火墙上只打开一个端口(标准端口)使管理变得更加容易。
我想找到类似的东西来进行 SSH 连接,但运气不佳。我不想简单地使用 SSH 隧道,因为这需要打开标准以外的端口范围。有什么可以做到这一点的吗?
HAProxy 可以做到这一点吗?
答案1
答案2
在过去的 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
如果真正的解决方案(IPv6)超出你的能力范围,那么可以通过一些肮脏的手段来让它发挥作用
我即将描述的黑客攻击只能作为绝对最后的手段。在您考虑使用此黑客攻击之前,我强烈建议为您希望通过 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
您觉得这看起来像 SSH 或 HTTP 吗?它是 SSH 并且完全符合 RFC 标准(除了一些二进制字符因 SF 渲染而有点损坏)。
SSH 版本字符串包含一个注释字段,在上面的值为/ HTTP/1.1
。换行符后是 SSH 的一些二进制数据包数据。第一个数据包是MSG_SSH_IGNORE
客户端发送的消息,被服务器忽略。要忽略的有效负载是:
:
Host: example.com
如果 HTTP 代理在接受内容方面足够宽松,那么相同的字节序列将被解释为调用的 HTTP 方法SSH-2.0-OpenSSH_6.6.1
,而忽略消息开头的二进制数据将被解释为 HTTP 标头名称。
代理既不能理解 HTTP 方法,也不能理解第一个标头。但它可以理解标Host
头,这是它找到后端所需的全部信息。
为了使其正常工作,代理必须按照这样的原则进行设计:它只需要理解足够的 HTTP 来找到后端,并且一旦找到后端,代理将简单地传递原始字节流,而将 HTTP 连接的真正终止留给后端完成。
对 HTTP 代理做出这么多假设听起来可能有点牵强。但如果你愿意安装一款专为支持 SSH 而开发的新软件,那么对 HTTP 代理的要求听起来就不算太高。
就我个人而言,我发现此方法适用于已安装的代理,无需更改代码、配置或其他方面。而且,这是针对仅考虑 HTTP 而未考虑 SSH 的代理编写的。
概念验证客户和代理人。(免责声明:代理是我运营的服务。一旦确认有其他代理支持此使用,请随意更换链接。)
这次黑客攻击的注意事项
- 不要使用它。最好使用真正的解决方案,即 IPv6。
- 如果代理尝试理解 HTTP 流量,它肯定会中断。
- 依赖修改后的 SSH 客户端并不好。
答案3
又有新人上场了。SSH piper 将根据预定义的用户名路由您的连接,这些用户名应映射到内部主机。这是反向代理环境下的最佳解决方案。
我的第一次测试证实,SSH 和 SFTP 均可运行。
答案4
我想知道具有代理模式的 Honeytrap(低交互蜜罐)是否不能通过修改来实现这一点。
此蜜罐能够将任何协议转发到另一台计算机。添加基于名称的虚拟主机系统(由 Apache 实现)可以使其成为任何协议的完美反向代理,不是吗?
我没有实现这一目标的技能,但也许这可能是一个伟大的项目。