你好,我是工人队的一名工程师,今天我想和你们谈谈安全问题。
CloudFlare是一家安全公司,在我看来,Workers的核心是一个安全项目。运行由第三方编写的代码总是一个可怕的问题,而工人团队主要关心的是确保安全。
对于这样的项目,仅仅通过安全审查并说“好了,我们安全了,然后继续前进”是不够的。在设计和实现的每个阶段考虑安全性都是不够的。对于工人来说,安全本身是一项持续的项目,而这项工作永远不会完成。我们总是可以做一些事情来降低未来漏洞的风险和影响。
今天,我想向您概述一下我们的安全体系结构,然后解决我们经常被问到的两个具体问题:V8错误和Spectre。
设计代码沙箱有两个基本部分:安全隔离和API设计。
首先,我们需要创建一个执行环境,在这个环境中,代码不能访问任何它不应该访问的东西。
为此,我们的主要工具是V8,这是Google开发的用于Chrome的JavaScript引擎。V8执行隔离内代码,这会阻止代码访问隔离外的内存--即使在同一进程内也是如此。重要的是,这意味着我们可以在单个进程中运行多个隔离。这对于工人这样的边缘计算平台至关重要,因为我们必须在每台机器上托管数千个访客应用,并以最小的开销每秒在这些访客之间快速切换数千次。如果我们必须为每个客户运行单独的流程,我们可以支持的租户数量将大幅减少,我们将不得不将EDGE计算限制在少数可以支付高额费用的大型企业客户。有了隔离技术,我们可以让每个人都可以使用边缘计算。
不过,有时我们确实会决定安排一个员工在其自己的私人流程中工作。如果它使用了我们觉得需要额外隔离的某些功能,我们就会这么做。例如,当开发人员使用DevTools调试器检查其Worker时,我们将在单独的进程中运行该Worker。这是因为从历史上看,在浏览器中,检查器协议只能由浏览器的可信操作员使用,因此没有像V8的其他版本那样接受过多的安全检查。为了规避检查器协议中增加的bug风险,我们将被检查的工作人员移到带有进程级沙箱的单独进程中。我们还使用进程隔离作为对Spectre的额外防御,我将在本文后面描述这一点。
此外,即使对于与其他隔离在共享进程中运行的隔离,我们也会在每台机器上运行整个运行时的多个实例,我们称之为“警戒”(cordons";)。通过为每个员工分配一个信任级别,并将低信任员工与我们高度信任的员工分开,从而在警戒线之间分配员工。操作中的一个例子是:注册我们的免费计划的客户将不会被安排在与企业客户相同的流程中。这在V8中发现零日安全漏洞的情况下提供了一些深度防御。但我将在这篇文章的后面讨论更多关于V8的错误,以及我们如何解决它们。
在全过程层面,我们在纵深防御上又加了一层沙盒。第二层沙箱使用Linux命名空间和seccomp来禁止对文件系统和网络的所有访问。名称空间和seccomp通常用于实现容器。然而,我们对这些技术的使用比容器引擎中通常可能的要严格得多,因为我们在进程启动之后(但在加载任何隔离之前)配置名称空间和seccomp。这意味着,例如,我们可以(并且确实)使用完全空的文件系统(挂载名称空间),并使用seccomp完全阻止所有与文件系统相关的系统调用。容器引擎通常不能禁止所有文件系统访问,因为这样做会使使用exec()从磁盘启动来宾程序变得不可能;在我们的例子中,来宾程序不是本机二进制程序,并且在我们阻止文件系统访问之前,Worker运行时本身已经完成加载。
第2层沙箱也完全禁止网络访问。相反,该进程仅限于通过本地Unix域套接字进行通信,以便与同一系统上的其他进程通信。任何与外部世界的通信都必须由沙箱之外的某个其他本地进程进行调解。
其中一个特别的进程,我们称之为主管进程,负责从磁盘或其他内部服务获取工作程序代码和配置。主管确保沙箱进程不能读取任何配置,但与其应该运行的工作进程相关的配置除外。
例如,当沙箱进程收到对它以前从未见过的工作进程的请求时,该请求包括该工作进程代码的加密密钥(包括附加的机密)。然后,沙箱可以将该密钥传递给主管,以便请求代码。沙箱不能请求它没有收到适当密钥的任何工作器。它不能枚举已知的工作者。它也不能请求它不需要的配置;例如,它不能向工作者请求用于HTTPS通信的TLS密钥。
除了读取配置之外,沙箱与系统上的其他进程对话的另一个原因是实现向Worker公开的API。这就引出了API设计。
有一种说法:如果一棵树倒在森林里,但没有人听到它的声音,它会发出声音吗?";我有一个相关的说法:如果一个工人在完全与世隔绝的环境中执行任务,在这个环境中,它完全被阻止与外界交流,它真的会跑吗?";
实际上,完全的代码隔离是无用的。为了让工人做任何有用的事情,他们必须被允许与用户交流。至少,工作人员需要能够接收并响应请求。如果它能安全地向世界发送请求,那就太好了。为此,我们需要API。
在沙箱环境中,API设计承担了新的责任。我们的API准确定义了员工可以和不可以做什么。我们必须非常小心地设计每个API,使其只能表达我们想要允许的操作,而不能超过这些。例如,我们希望允许Worker发出和接收HTTP请求,而不希望他们能够访问本地文件系统或内部网络服务。
让我们先深入研究一下较简单的例子。目前,Worker不允许对本地文件系统进行任何访问。因此,我们根本不公开文件系统API。没有API表示没有访问权限。
但是,想象一下,如果我们希望在将来支持本地文件系统访问。我们要怎么做呢?我们显然不希望工人看到整个文件系统。但是,想象一下,我们希望每个Worker在文件系统上都有自己的私有目录,它可以在其中存储任何它想要的东西。
为此,我们将使用基于功能的安全性设计。功能是一个很大的主题,但在本例中,这意味着我们将为工作者提供一个Directory类型的对象,表示文件系统上的一个目录。此对象将具有允许创建和打开文件和子目录的API,但不允许向上遍历树到父目录。实际上,每个工作人员都会将其私有目录视为自己文件系统的根目录。
这样的API将如何实现呢?如上所述,沙箱进程不能访问真实的文件系统,我们更愿意让它保持这种状态。相反,文件访问将由主管进程进行调解。沙箱使用Cap';n Proto RPC(一种基于功能的RPC协议)与主管进行对话。(Cap&39;n Proto是一个开源项目,目前由Cloudflare Workers团队维护。)。该协议使得实现基于功能的API变得非常容易,因此我们可以将沙箱严格限制为只访问属于它正在运行的Worker的文件。
现在,网络访问情况如何呢?今天,工人们只能通过HTTP与世界其他地方交谈--无论是传入的还是传出的。其他形式的网络访问没有API,因此被禁止(虽然我们计划将来支持其他协议)。
如前所述,沙箱进程不能直接连接到网络。相反,所有出站HTTP请求都通过Unix域套接字发送到本地代理服务。该服务对请求实施限制。例如,它验证请求是发往公共Internet服务,还是发往员工区域自己的源服务器,而不是发往可能在本地计算机或网络上可见的内部服务。它还向每个请求添加一个标头,以标识发起该请求的Worker,以便可以跟踪和阻止滥用请求。一旦一切就绪,请求就会被发送到我们的HTTP缓存层,然后再发送到Internet。
同样,入站HTTP请求不会直接转到Worker运行时。它们首先由入站代理服务接收。该服务负责TLS终止(Worker Runtime永远不会看到TLS密钥),以及标识要为特定请求URL运行的正确Worker脚本。一旦一切就绪,请求就会通过Unix域套接字传递到沙箱进程。
每一个重要的软件都有缺陷,沙盒技术也不例外。虚拟机有bug,容器有bug,是的,隔离(我们使用的)也有bug。我们不能生活在假装再也不会发现错误的情况下;相反,我们必须假设他们会发现,并相应地做好计划。
我们在很大程度上依赖于V8提供的隔离,V8是Google为在Chrome中使用而构建的JavaScript引擎。这有好的一面,也有坏的一面。一方面,V8是一项极其复杂的技术,它创造了比虚拟机更广泛的攻击面。复杂性越高,出错的机会就越多。不过,好的一面是,由于V8可以说是世界上最流行的沙盒技术,在寻找和修复V8错误方面投入了大量的精力。谷歌定期向任何找到V8沙盒逃生的人支付5位数的赏金。谷歌还运营着模糊基础设施,自动查找漏洞的速度比大多数人都快。谷歌的投资大大降低了V8零日漏洞的风险,这些漏洞是坏人发现的,谷歌并不知道。
但是,在好人发现并报告了错误之后会发生什么呢?V8是开放源码的,所以针对安全错误的修复是公开开发的,并同时向所有人发布--好人和坏人。重要的是,在坏人开发漏洞之前,任何补丁都要尽快投入生产。
从发布修补程序到部署修补程序之间的时间称为补丁程序间隙。今年早些时候,谷歌宣布Chrome的补丁间隔从33天缩短到15天。
幸运的是,我们在这里有一个优势,那就是我们可以直接控制系统在其上运行的机器。我们已经实现了几乎整个构建和发布过程的自动化,因此一旦发布了V8补丁,我们的系统就会自动构建一个新版本的Workers Runtime,并且在必要的(人工)审查者单击签字后,会自动将该版本推向生产。
因此,我们的补丁间隔现在不到24小时。V8&39;在慕尼黑的团队在工作日发布的补丁通常会在我们在美国的工作日结束之前投入生产。
我们收到了很多关于幽灵党的问题。谷歌的V8团队已经明确表示,V8本身不能防御Spectre。由于工人依赖V8进行沙箱,许多人问这是否会让工人变得脆弱。然而,我们不需要依赖V8来实现这一点;Worker环境提供了许多替代方法来缓解Spectre。
幽灵是复杂和微妙的,我不可能在一篇博客文章中涵盖关于它的所有知识,或者工人们是如何解决它的。但是,希望我能澄清一些困惑和担忧。
SPECTE是一类攻击,在这种攻击中,恶意程序可以诱使CPU使用程序不应该访问的数据推测性地执行计算。CPU最终意识到这个问题,不允许程序看到推测计算的结果。然而,该程序可能能够通过查看计算的细微副作用(例如对高速缓存的影响)来推导出秘密数据的比特。
SPECTE包含了现代CPU中存在的各种漏洞。具体的漏洞因体系结构和型号而异,很可能存在许多尚未发现的漏洞。
这些漏洞是每个云计算平台的问题。任何时候,只要您有多个租户在同一台计算机上运行代码,Spectre攻击就会开始发挥作用。然而,租户离得越近,就越难以缓解特定的漏洞。许多已知问题可以在内核级别(相互保护进程)或虚拟机管理程序级别(保护VM)得到缓解,通常借助CPU微码更新和各种技巧(不幸的是,其中许多都会带来严重的性能影响)。
在Cloudflare Workers中,我们使用V8隔离将租户彼此隔离--而不是进程或VM。这意味着我们不一定要依赖操作系统或虚拟机管理程序补丁来为我们解决问题。我们需要我们自己的战略。
CloudFlare Workers设计用于在每个CloudFlare位置运行您的代码,目前全球有200个CloudFlare位置,而且还在不断增加。
我们希望Worker成为一个人人都可以访问的平台--而不仅仅是那些可以支付百万美元的大企业客户。我们需要处理大量的租户,其中许多租户的流量非常小。
典型的非边缘无服务器提供商可以通过将低流量租户的所有流量发送到一台计算机来处理该租户,因此只需要加载应用程序的一个副本。比方说,如果这台机器能处理十几个租户,那就足够了。这台机器可以托管在一个拥有数百万台机器的巨型数据中心,实现规模经济。然而,当用户碰巧不在附近时,这种集中化会招致延迟和全球带宽成本。
另一方面,有了Worker,无论流量级别如何,每个租户目前都可以在每个Cloudflare位置运行。在我们寻求尽可能接近最终用户的过程中,我们有时会选择仅有空间容纳有限数量的机器的位置。最终结果是,我们需要能够在每台计算机上托管数千个活动租户,并能够按需快速启动非活动租户。这意味着每个来宾不能占用超过几兆字节的内存--几乎没有足够的空间来放置调用堆栈,更不用说进程所需的所有其他内容了。
此外,我们需要非常便宜的上下文切换。许多驻留在记忆中的工人只会时不时地处理一个事件,而且许多工人在任何特定事件上花费的时间都不到零点几毫秒。在这种环境中,单个核心很容易发现自己每秒都会在数千个不同的租户之间切换。此外,要处理一个事件,需要在客户应用程序和其主机之间进行大量通信,这意味着更多的交换和通信开销。如果每个租户都生活在自己的进程中,那么所有这些开销都比多个租户生活在单个进程中要大几个数量级。在Worker中使用严格的进程隔离时,我们发现CPU成本很容易达到共享进程的10倍。
为了让员工保持廉价、快速和人人都能访问,我们必须解决这些问题,这意味着我们必须找到一种方法,在单个进程中托管多个租户。
该行业不愿承认的一个肮脏的秘密是:没有人修复过“幽灵党”。即使在使用重量级虚拟机时也不会。每个人都仍然脆弱不堪。
目前大多数行业采取的方法本质上是一场打地鼠的游戏。每隔几个月,研究人员就会发现一个新的Spectre漏洞。CPU供应商发布了一些新的微码,操作系统供应商发布了内核补丁,每个人都必须更新。
很明显,还有更多的漏洞存在,但尚未公之于众。谁会知道这些漏洞呢?发布的大多数错误都是由(非常聪明的)研究生用很少的预算发现的。想象一下,一个资金雄厚的政府机构,能够买到世界上最优秀的人才,还能发现多少漏洞。
为了真正防御幽灵党,我们需要采取一种不同的方法。它不足以阻止单个已知漏洞。我们必须立即解决整个类别的漏洞。
不幸的是,它不太可能找到任何包罗万象的幽灵修复方法。但为了便于讨论,让我们试一试。
基本上,所有Spectre漏洞都使用旁路来检测隐藏的处理器状态。根据定义,侧通道包括观察系统的一些不确定行为。方便的是,大多数软件执行环境都在努力消除不确定性,因为非确定性执行会使应用程序不可靠。
然而,有几种非决定论仍然很常见。其中最明显的是时机。业界很早以前就放弃了程序每次运行都应该花费相同时间的想法,因为确定性的时间安排与启发式的性能优化从根本上是不一致的。果不其然,大多数Spectre攻击都集中在计时上,将其作为检测CPU隐藏的微体系结构状态的一种方式。
一些人提出,我们可以通过使定时器不准确或添加随机噪声来解决这个问题。然而,事实证明,这并不能阻止攻击;它只会使攻击变得更慢。如果计时器完全跟踪实时,则可以通过多次运行攻击并使用统计数据过滤噪音来克服任何可能导致其不准确的问题。
许多安全研究人员认为这就是故事的结局。如果攻击仍然有可能,减缓攻击又有什么用呢?一旦攻击者拿到你的私钥,游戏就结束了,对吗?他们花一分钟还是一个月的时间有什么不同呢?
我们的主要见解是:当攻击变得更慢时,新的技术变得实用,使它变得更慢。因此,我们的目标是将足够多的技术串联在一起,使攻击变得如此缓慢,以至于变得乏味。
毕竟,许多密码学在技术上很容易受到暴力攻击--从技术上讲,只要有足够的时间,你就可以破解它。但当所需的时间是数千年(甚至数十亿年)时,我们认为这就足够了。
那么,我们要怎么做才能把幽灵攻击减慢到毫无意义的地步呢?
我们不允许我们的客户上传在我们网络上运行的本机代码二进制文件。我们只接受JavaScript和WebAssembly。当然,许多其他语言,如Python、Rust,甚至Cobol,都可以编译或转换为这两种格式中的一种。
.