Ubuntu 20.04 原生 WireGuard 初体验

前不久 Ubuntu 20.04 正式 release 了,挺多期待已久的新特性都转正成为了“原生”,如zfs、WireGuard. 而作为一名 gopher, 我留意到,负责维护 golang 标准库中网络相关(net/http)的 committer Bradfitz 前不久从Google离职加入了基于 WireGuard 技术创业公司 Tailscale. 这引发我强烈的好奇,因此 Ubuntu 20.04 发布,自然是要体验一下 WireGuard.

WireGuard 的关键特性

WireGuard 一直宣传自己是现代的快速安全的VPN。快速主要体现在部署的便捷、高性能(通过内核原生+CPU友好的加密算法实现);安全则是从设计之初就考虑到了了前辈的一些安全问题,以及学院派研究的加持,更多细节可以参考 technical whitepaper. 当然,从当前的limitation看,ID还无法做到前向安全,因此无法做到 no-tracking。

WireGuard 安装及配置

安装直接参考官方Installation文档,配置可以参考 Set Up WireGuard VPN on Ubuntu.

几点疑问及思考

WireGuard 能作为你特殊用途协议吗?

不能。主要原因有两点:

  • 协议没有做混淆,也没有隐藏包特征,过不了DPI(Deep Packet Inspection)检测。
  • ID非前向安全。因此通信一方compromised以后,会泄露历史通信peer.

WireGuard Server 支持 DDNS 吗?

支持。在一些场景,可能 server 端并没有一个固定IP,这种时候我们一般是使用DDNS将动态IP绑定在一个域名上面。这种场景下,配置client端配置时,将Server Public IP配置为域名即可:

[Peer]
PublicKey = <server public="" key="">
Endpoint = <server ddns="" domain="">:51820
AllowedIPs = 10.0.0.2/24, fd86:ea04:1115::5/64

WireGuard Server 是否可以动态添加 peer?

不可以也可以。因为 WireGuard 理念认为提前设计和分配自己的网络更佳好的方式。不过,也并不是完全没有work around的方式。如果你有动态IP和配置的需求,个人用户可以参考wg-dynamic, 企业用户推荐直接使用 Tailscale 的产品。

一个出生在内核的VPN到底有何特殊的意义?

回到本源,VPN本身的目的是什么?私以为是anytime, anywhere, 简单、快速、安全的网络互联。而这与WireGuard的设计目标是完全一致的。互联网底座这几年的发展趋势之一是加密整个互联网:元老级应用层协议http在发展过程中不断进化,来到http/2 的时候我们看到的一个显著改变那就是 mutilplex 和 force https。而作为更加底层的网络基建VPN却一直没有出现这样的角色。WireGuard的出现让我们看到了这个可能,但也只是一个更好的VPN的定位。而直到20.04确认将其加入内核,为其加密整个网络插上翅膀,我们才有理由相信这个角色到位了。

从这个角度来看,WireGuard无限接近理想的VPN,或者说它重新定义了VPN的初心。因此,不要拿个人日常运动小需求来揣测WireGuard的鸿鹄之志。顺着这个方向继续看,可以看到 WireGuard 的(潜在)应用场景有:

  • 远程办公网络基建。传统的VPN能不能解决?能。但是都解决得不好,要么是部署太过复杂,要么协议本身就因为历史原因漏洞一大把。而WireGuard部署成本极低(如果你按照上述文档部署一次,应该不会超过10分钟),全客户端支持,code base 只有4000行,非常容易发现、修复、迭代。而这次疫情的出现,让远程办公成为新常态推进了一大步。但是扪心自问,有多少所谓的远程办公是战斗力残废,又有多少其实是临时让IT开了个网络口子裸奔办公?因此,私以为基于WireGuard的远程办公在未来一两年是能看到几家公司成长起来的。
  • 异地多活架构。K8S生态解决单IDC可用性问题是足够的,但是在当前越来越强调稳定、体验产品时代,很多公司都开始走上异地多活的架构。WireGuard可能带来两个契机:1. 云厂商IDC互通的大一统;2. 自主异地多活架构的标准化,而这可能带来一波IDC异地多活网络中间件的开源生态繁荣。
  • 智能家居网络路由器。虽然现在的家电只要搭配一个wifi就号称自己是智能家电,但大多数不过是把家电连到厂家的服务器。这只能算是智能家电社会主义初级阶段,未来理想的形态是家庭中的设备与业主anytime, anywhere的互通。以前的解决方案都太trick了,而WireGuard找了个好爹,并且其极低的门槛都有希望在这个系分领域有所建树。

而以上这些,不过是 Bradfitz 的新东家 Tailscale 已经发布的部分创业产品。过两年以后,我会回头再次审视以上判断,说不定,上面的判断全都错了。

Go 1.12 TLS 1.3 简单测试

《TLS 1.3 当前(2018.10)支持与部署之现状》中,我们提到 Go 将在 1.12 中支持 TLS 1.3. 作为一个 Gopher, 终于在前几天盼来了 golang 1.12 的发布。

但是从 release 日志看,本次对选择性的部分支持 TLS 1.3, 且默认处于关闭状态:

Go 1.12 adds opt-in support for TLS 1.3 in the crypto/tls package as specified by RFC 8446. It can be enabled by adding the value tls13=1 to the GODEBUG environment variable. It will be enabled by default in Go 1.13.

如果要开启 TLS 1.3, 需要设置环境变量:GODEBUG=tls13=1.

本次发布的 TLS 1.3 的 cipher suite 无法配置,也不支持 0-RTT 模式:

TLS 1.3 cipher suites are not configurable. All supported cipher suites are safe, and if PreferServerCipherSuites is set in Config the preference order is based on the available hardware.

Early data (also called “0-RTT mode”) is not currently supported as a client or server.

要知道,没有 0-RTT 的 TLS 1.3 是没有灵魂的,对本次版本的失望那是肯定的。但是依然在第一时间升级了 Go 版本,简单测试了一下国内网络环境下 TLS 1.3 与 1.2 的握手延迟。如果对 TLS 1.3 握手延迟还不太熟悉,可以参见拙文《TLS1.3/QUIC 是怎样做到 0-RTT 的》 以及 TLS 1.3 Handshake Protocol.

测试代码

需要说明的是,这个测试是不严谨,里面没有考虑 cipher suite 以及 early data 的差异。测试结果定性意义大于定量意义。

测试结果

测试使用的目标服务器地址是 blog.cloudflare.com:443, 我的网络环境下,ping 延迟为 226 ms (1-RTT).

从结果看,有如下结论:

  1. TLS 1.3 平均比 TLS 1.2 建立连接的延迟低约 1-RTT, 跟理论分析是吻合的。但是,
  2. 从 max 项可以看出,部分时候国内到目标服务器网络不稳定带来的波动比 TLS 本身协议优化的 RTT 大的多。因此,稳定高延迟的网络链路有时候比低延迟高抖动的网络更有实际意义。
  3. TLS 建立在 TCP 基础上,TCP 的握手延迟在 TLS 层面是优化不掉的,或者说不是 TLS 的管辖范围,因此,在允许的情况下,尽量复用连接。

TLS 1.3 当前(2018.10)支持与部署之现状

今年8月10日,历经三年有余,TLS 1.3 最终版本终于得以发布——RFC 8446. 关于 RFC 的详细介绍可以进一步阅读 A Detailed Look at RFC 8446 (a.k.a. TLS 1.3). TLS 1.3 因为其在握手延迟以及安全性上的改进 (可以参考拙文《TLS1.3/QUIC 是怎样做到 0-RTT 的》),毫不夸张的说,这是一件将深刻而长远影响互联网发展的技术里程碑。那么将 TLS 1.3 尽快平滑应用到线上环境无疑是一件势在必行的事情了。

在我日常的工作中,对于 TLS 1.3 的支持和部署主要关注两个层面: 编程语言(Go, Java)、 API gateway (Nginx) 和浏览器. 下面分别介绍一下这三个层面 TLS 1.3 的支持部署现状。(因为 RFC 8446 已经发布,因此本文中说的支持如无特殊说明,都是指对最终版本 RFC 8446 的支持。)

编程语言对 TLS 1.3 的支持

Go 方面,官方在 TLS 1.3 draft 阶段一直没有跟进。因此,有一个关于对 TLS 1.3 支持的 issue 从 2015 年 open 至今都没有关闭:crypto/tls: add support for TLS 1.3. 其实 Go 发布版本和改进标准库的效率还是挺高的,对于 TLS 1.3 上的“不作为”更多是因为 Go 在兼容性上的承诺导致其并适合在最终版发布前实现互不兼容的 draft 方案。

而 Go 1.11 的发布时间(2018.08.24)与 RFC 8446 的发布时间比较接近,没有足够时间实现并发布该特性。从 golang-dev 小组讨论 Re: crypto/tls and TLS 1.3 看,由于 1.11 没有实现 TLS 1.3 ,那么 1.12 实现 TLS 1.3 基本是板上钉钉的事了:

The key scheduling fact is that the Go 1.11 feature freeze is just a week away, so we decided that it would be too rushed to merge the 1.3 patches for it. I definitely aim to have TLS 1.3 in Go 1.12.

根据惯例, Go 1.12 的发布时间将会是 2019.02~03. 如果期间你实在想用 Go 编程测试 TLS 1.3, 可以尝试使用 CloudFlare 的 tls-tris 库。根据 Go net/http 标准库维护者 Brad Fitzpatrick 的消息,这个库将会被合并到标准库作为 Go 官方 TLS 1.3 的实现。因此,如果你不得不用这个库干一些生成环境的活也大可放心,即使日后升级 Go 1.12, 接口兼容性还是有保证的。

Java 方面,由于 Java 11 出生时间好(2018.09.25), 因此是出生就支持 TLS 1.3 RFC 8446, 具体可以参见 JEP 332: Transport Layer Security (TLS) 1.3. Java 11 是 LTS 版本,因此,如果有条件升级到 11, 推荐使用 Java 11 实现的 TLS 1.3 以及配套的 HttpClient;如果生产环境暂无法升级 Java 版本,推荐使用 OkHttp. 关于 Java Http Client 选型可以参见Java HTTP 组件库选型看这篇就够了

Nginx 对 TLS 1.3 的支持

准确讲应该是 Nginx 所使用 SSL lib 对 TLS 1.3 的支持。在这方面,Boring SSL 跟进速度飞快,在 RFC 发布后第4天实现了对最终版本的支持。OpenSSL 虽然很早就跟进了 draft 的实现,但是对最终版本的支持需要 1.1.1-pre9 及以后的版本:

The OpenSSL git master branch (and the 1.1.1-pre9 beta version) contain our development TLSv1.3 code which is based on the final version of RFC8446 and can be used for testing purposes (i.e. it is not for production use). Earlier beta versions of OpenSSL 1.1.1 implemented draft versions of the standard. Those versions contained the macro TLS1_3_VERSION_DRAFT_TXT in the tls1.h header file which identified the specific draft version that was implemented. This macro has been removed from 1.1.1-pre9 and the current master branch.

TLSv1.3 is enabled by default in the latest development versions (there is no need to explicitly enable it). To disable it at compile time you must use the “no-tls1_3” option to “config” or “Configure”.

Although the latest 1.1.1 versions support the final standard version, other applications that support TLSv1.3 may still be using older draft versions. This is a common source of interoperability problems. If two peers supporting different TLSv1.3 draft versions attempt to communicate then they will fall back to TLSv1.2.

而第一个 OpenSSL 1.1.1 release 是在 2018.09.11, 因此如果你跟我一样是 OpenSSL 的死忠粉,当前阶段 Nginx 支持 TSL 1.3 的最佳方式是 Nginx 1.15.5 + OpenSSL 1.1.1. 而这种脏活、苦活、累活当然是交给 Docker 解决了:从源代码编译 nginx docker 镜像开启 TLS 1.3,项目地址可以参见 docker-nginx.

配置 Nginx 支持 TLS 1.3 需要注意一点:默认情况下 Nginx 因为安全原因,没有开启 TLS 1.3 0-RTT,可以通过添加 ssl_early_data on; 指令开启 0-RTT. 完整配置可以参考 nginx.conf.

浏览器对 TLS 1.3 的支持

当前阶段,Chrome 69 和 Firefox 62 都只支持到 draft 28, 而 draft 28 与最终版本是不兼容的。因此,要测试体验 TLS 1.3 final 需要使用 Chrome Beta 测试版。然后在 chrome://flags/#tls13-variant 开启 TLS 1.3 final:

扩展阅读