SSH和用户模式IP WireGuard

2021-03-02 22:53:11

您可能想对托管在Fly.io之类的提供程序上的应用程序进行处理:在其上弹出一个shell。

但是Fly有点奇怪。我们在世界各地的数据中心中运行硬件,这些数据中心通过Anycast连接到Internet,并通过WireGuard网格相互连接。我们从用户那里获取Docker型容器,并将其迁移到Firecracker微型VM中。而且,当我们刚开始时,我们做了所有这些工作,以便我们的客户可以运行“边缘应用程序”;通常,相对较小的自包含代码段对网络性能特别敏感,并且需要靠近用户运行。在那种环境中,能够通过SSH连接到应用程序并不重要。

但这并不是所有人现在都使用Fly。今天,您可以轻松地在Fly上运行整个应用程序。我们简化了在群集配置中运行服务的过程,这些服务可以彼此私下对话,持久存储数据并通过WireGuard与他们的运营商对话。而且,如果我继续这样写作,我可能可以标记过去几个月来我们撰写的每篇博客文章。

现在,当然,您只需构建一个运行SSH服务的容器,然后将SSH插入其中即可。 Fly支持原始TCP(和UDP)联网;如果您在fly.toml中向我们的Anycast网络介绍了您怪异的SSH端口,那么我们会将SSH连接路由给您,就可以了。

但这不是人们想要构建容器的方式,我们也没有要求他们这样做。因此,我们构建了SSH功能。很古怪。我在这里分两部分对其进行描述。

我在Fly写了很多有关私人网络的文章。长话短说:它就像是GCP或AWS“虚拟私有云”的更简单的IPv6版本;我们称之为“ 6PN”。在Fly启动应用程序实例(Firecracker微型VM)时,我们为其分配了特殊的IPv6前缀。该前缀对应用程序的ID,其组织的ID以及正在运行的Fly硬件的标识符进行编码。我们使用少量的eBPF代码沿内部WireGuard网状网络静态路由这些IPv6数据包,并确保客户不会跳入不同的组织。

此外,您可以使用WireGuard将我们创建的专用IPv6网络与其他网络桥接。我们的API将创建新的WireGuard配置,您可以将它们粘贴在EC2主机上以代理RDS Postgres。或者,根据需要使用Windows,Linux或macOS WireGuard客户端将开发计算机连接到专用网络。

我们在Go中编写了一个小巧的微型SSH服务器,称为Hallpass。实际上,它是Go的x / crypto / ssh库的“ hello world”。 (如果我要再做一次,我可能只会使用Gliderlabs SSH服务器库,从字面上看就是“ hello world”)。我们的爆竹“ init”在所有实例上启动Hallpass,绑定到实例的6PN地址。

如果您可以与组织的6PN网络通信(例如,通过WireGuard连接),则可以使用Hallpass登录到您的实例。

关于Hallpass的工作原理,只有一件有趣的事情,那就是身份验证。生产网络中的基础结构通常无法直接访问我们的API或支持它的数据库,当然,实例本身也不能。这使得交流配置更改(如“允许哪些键登录到实例”)有点项目。

我们通过使用SSH客户端证​​书来回避这项工作。我们不是为用户每次要从新主机登录而传播密钥,而是为他们的组织建立一次性的根证书。该根证书的公共密钥托管在我们的私有DNS中,每次通过登录尝试,Hallpass都会咨询DNS来解析证书。我们的API为可用于登录的用户签署了新证书。

第一:证书。数十年的X.509疯狂可能在您的嘴中留下了“证书”的不良味道。我不怪你但是您应该对SSH使用证书,因为它们很棒。 SSH证书不是X.509;它们是OpenSSH自己的格式,对它们没有太多影响。像所有证书一样,它们都有到期日期,因此您可以创建短期密钥(几乎总是您想要的密钥)。而且,当然,它们允许您在整堆服务器上设置单个公钥,该公钥可以授权任意数量的私钥,而无需重复更新这些服务器。

下一步:我们的API和签名证书。哭!我们非常小心,但它基本上与您的Fly访问令牌一样安全;目前,它还没有比这更安全的方法,因为您的访问令牌允许您部署应用容器的新版本。 WebPKI X.509 CA签名涉及很多仪式;这不是那样,我们很礼貌。

最后是DNS。我承认,这似乎是废话。但这比看起来少得多。我们在其上运行实例的每个主机都运行私有DNS服务器的本地版本(一个小的Rust程序)。 eBPF代码可确保您只能从服务器的6PN地址与该DNS服务器进行对话(从技术上讲:您只能查询该服务器的私有DNS API;任何人都可以对其进行递归)。 DNS服务器可以(我知道这很奇怪)可以可靠地从源IP地址识别您的组织身份。这就是我们要做的。

所有这些事情都在幕后发生。作为用户,您所看到的是命令flyctl ssh issue -a,它将从我们的API中获取新证书并将其插入到本地SSH代理中,此时SSH可以正常工作。整洁。

这是一个问题:并非每个人都设置了WireGuard。他们应该; WireGuard很棒,它对于管理在Fly上运行的应用程序非常有用。但是,无论如何,有些人没有。

乍一看,未安装WireGuard似乎很麻烦。 WireGuard的工作方式是,您在计算机上获得一个新的网络接口,或者是内核模式的WireGuard接口(在Linux上),或者是连接了用户级WireGuard服务的隧道设备(在其他任何地方)。没有该网络接口,您将无法通过WireGuard进行通话。

但是,如果您斜视着WireGuard并以正确的方式倾斜头部,您会发现这在技术上是不正确的。您需要操作系统特权才能配置新的网络接口。但是您不需要任何特权即可将数据包发送到51820 / udp。您可以将整个WireGuard协议作为无特权的用户区进程来运行,这就是Wireguard-go的工作方式。

这使您可以尽可能地与WireGuard握手。但是,您仍然不能通过WireGuard网络进行通信,因为您不能只是将随机字符串发送到WireGuard连接的另一端。您的对等方希望使用TCP / IP数据包。您计算机上的本机套接字接口无法帮助您通过随机连接的UDP套接字建立TCP连接。

仅仅为了进行纯用户域WireGuard网络的目的,将一个微小的用户模式TCP组合在一起有多难,这样人们就可以在不安装WireGuard的情况下将SSH连接到Fly实例中?

我在与Jason Donenfeld分享的Slack频道上迷恋了这个错误。我上床睡觉之前就在沉思。我醒了。 Jason使用gVisor实现了它,并使其成为WireGuard库的一部分。

这里的窍门是gVisor。我们之前已经写过有关它的文章。对于不熟悉的人来说,gVisor是Linux,可以在用户域中运行,可以在Golang中重新实现,作为容器runc的替代品。这是一个裤子衬衫香蕉疯子项目,如果您运行它,我认为您应该吹嘘它,因为哇很多。而且,用Go语言编写的一个完整的TCP / IP实现埋藏在其内胆中,其输入和输出仅为[] byte缓冲区。

一些推文是成对的,几个小时后,我收到了本·伯克特(Ben Burkert)的一封友好电子邮件。 Ben在其他地方做过gVisor联网工作,对我们的工作很感兴趣,并想知道我们是否要合作。对我们来说听起来不错!简而言之,我们现在有了基于证书的SSH的实现,该协议运行在gVisor用户模式TCP / IP之上,运行在flyctl中内置的userland wireguard-go之上。

为了给您一些香蕉的观点:dogmatic-potato-342.internal是内部DNS名称,仅通过6PN网络上的私有DNS进行解析。之所以可以在这里工作,是因为在ssh shell模式下,flyctl使用的是gVisor的用户模式TCP / IP堆栈。但是gVisor不提供DNS查找代码!那只是Go标准库,它已经被困扰于使用我们的bunko TCP / IP接口。

顺便说一句,Flyctl是开源的(必须是开源的;人们必须在他们的开发机器上运行它),这样您就可以阅读代码了。 Ben写道,pkg下的好代码。在其他地方,噩梦代码是我。用户模式IP WireGuard在Go中非常简单。就像,如果您以前做过底层TCP / IP工作:您可能不会相信它是如此简单:对象gVisor的TCP堆栈代码将直接插回标准库的网络代码中。

只需两百行代码(不计算您将从gVisor引入的整个用户模式Linux,嘿!依赖!您将要做什么!),您可以随时建立一个经过密码验证的新网络。想要在几乎任何程序中都想要。

当然,它比内核TCP / IP慢得多。但是,这真的有多重要呢?尤其是,对于您通常为之构建怪异的TLS隧道的随机任务,它的频率有多大?当事情开始变得重要时,您可以交换真正的WireGuard。

无论如何,它为我们解决了一个巨大的问题。不只是SSH,还包括我们还托管Postgres数据库,无论您是否可以使用一个简单的命令立即安装macOS WireGuard,都可以在任何地方打开psql shell非常方便。

另外,本·伯克特(Ben Burkert)很棒,你们都应该提高他的咨询率,因为他在几个小时内就完成了这个项目。 除了Tailscale今天早上在推特上发布有关他们将推出的gVisor TCP / IP功能的消息外,我会更优美地写有关他,Jason,gVisor以及整个项目的信息,而我会赢了 ;被殴打。 这次不是,尾鳞! 这次不行!