手机版 |  广告合作 |  投稿说明 |  下载声明 | 
IT新闻

Cloudflare 7.2 全球瘫痪罪魁祸首:.*(?:.*=.*)


导读: Cloudflare7月12日发布了一份详细如实的报告,披露了本月初那次故障的根本原因,当时其系统出岔子后,互联网上的一大批系统和服务随之遭殃。  持续30分钟的全球性故障是该公司用于快速推送软件变

 Cloudflare7 月 12 日发布了一份详细如实的报告,披露了本月初那次故障的根本原因,当时其系统出岔子后,互联网上的一大批系统和服务随之遭殃。


  持续 30 分钟的全球性故障是该公司用于快速推送软件变更系统中单单一行代码的错误引起的。


  尽管该软件变更事先已经过测试,但那次失误还是致使 Cloudflare 的服务器 CPU 不堪重负,并导致全球客户从 Cloudflare 支持的网站得到 502 错误信息。完整的事后分析报告深入探究了问题的具体根源以及这家公司之前和现在为解决问题并防止重蹈覆辙而采取的措施。

3.jpg


  简而言之,正是一连串的小错误导致了一次严重的故障。我们忍不住想使用“完美风暴”(糟得不能再糟的局面)这个流行的短语,但它不是完美风暴。最初只是个小错误,Cloudflare 其他方面很可靠的流程存在的诸多不足让这个小错误越来越严重。


  先说说错误本身——错误出在这段代码中:.*(?:.*=.*)。很笼统地说,这段代码引发了许多所谓的“回溯”(backtracking),这基本上就是重复循环。请求越复杂,这种回溯变得越糟糕——糟糕程度剧增,很快使该公司的 CPU 不堪重负。


2.jpg


  于是有了这三大问题:为什么这段代码在上线之前没人注意到?它为何如此迅速地带来如此巨大的影响?为什么 Cloudflare 花这么长的时间来修复?


  该博文详细清楚地回答了每个问题,甚至含有大多数公司在内部流程和软件方面不情愿透露的大量信息,因此为 Cloudflare 点个赞。


  我看到你了,CPU  


  就因为测试套件没有测量 CPU 的使用率这个简单原因,所以没人注意到影响。测试套件很快会测量,Cloudflare 内部规定一周内完成测量。


  第二个问题是,本可以防止 CPU 被过度使用的软件保护系统就在几周前被“误删除”了。这种保护现在又有了,不过显然需要牢牢锁定。


  用来运行代码的软件(表达式引擎)也无法检查出现的那种回溯。Cloudflare 表示会改用拥有检测功能的软件。


  那么,它是如何完成检查过程的:它影响每个人的速度怎么样?


  这是另一个重大错误:在对其 Web 应用程序防火墙(WAF)进行更改这方面,Cloudflare 似乎很有把握。WAF 旨在能够迅速为 Cloudflare 客户提供保护——它可以在短短几秒钟内在全球范围进行更改。


  Cloudflare 过去在这方面一直做得很好。它在博文中提到:5 月份快速部署了防范 SharePoint 安全漏洞的措施。漏洞公布后不久,该公司看到客户的系统屡遭黑客攻击,因而借助通过 WAF 推送的更新,几乎立马消灭了这些攻击。这种服务正是 Cloudflare 博得美名并赢得付费客户的法宝。它处理源源不断的安全问题,因此你不必操心。


  但它频频使用该系统:过去 60 天内有 476 次变更请求,或相当于每 3 小时一次请求。


  编写导致问题的代码是为了应对该公司已确认的新的跨站脚本(XSS)攻击,但是进行此变更并不紧迫,这是关键所在。因此,Cloudflare 本来可以较缓慢地引入该代码,在成为全球性问题之前注意到该问题。但它并没有;它有众多屡试不爽的测试流程,于是它将表达式部署到全局系统,就像对待其他许多表达式那样。


  Cloudflare 为此提到了每年发布的越来越多的 CVE(常见漏洞披露)。


  又见作战演习


  然而影响是,它立即引发了全球性故障。此外,代码本身在模拟模式下运行,而不是在标准的实际模式下运行,但由于它引发 CPU 被大量消耗,即使在模拟模式下,它也使所有服务都宕机,因为服务器处理不了处理负载。


  岔子就出在这里。现在,为什么 Cloudflare 花这么长的时间来修复?为什么它没有在查明情况的同时,在几分钟内执行回滚以解决问题?


  博文提供了一些令人关注的细节,这些细节对应对过危机的人来说很熟悉:问题在警报发布后引起注意,然后每个人乱成一团。问题只好逐级上报,请来更多的工程师,尤其是可以做出重大决定的更高级的工程师。


  这里的错误都是人为错误:首先,你得让别人在屏幕前、在电话那头和在聊天室。然后你得迅速而有效地协调。出了什么问题?原因是什么?我们怎么能确定办法是对的?


  面对压力,人容易恐慌,很容易误读或误解形势,或者做出错误的决定。要有冷静的头脑来搞清楚真相是什么,尽快找出解决问题的最佳方法。


  从 Cloudflare 的博文来看,这家互联网公司在这方面确实做得很好——由于时间表,我们对它描述的故障事件有一定的信心。尽管显然第一反应是该公司受到了某种外部攻击,但它在收到第一个警报的 15 分钟后就确定问题出在 WAF 上。考虑到没人监控这个规则变更,这个响应速度确实够快。出岔子的是日常更新。


  但是出现了几次关键的延误。首先,自动紧急警报三分钟后才到达。Cloudflare 承认这本可以更快。其次,尽管一名高级工程师在查明问题根源两分钟后决定对 WAF 进行全局终止,但实际处理这件事另外花了五分钟。


  慢慢死去


  原因何在?因为有权执行终止命令的人有一小会未登录进入到系统,该系统的保护系统因此将其注销退出。他们不得不重新验证身份才登录进入到系统。他们授权执行终止两分钟后,该命令在全球范围内开始生效,流量恢复正常——表明 WAF 确实就是问题所在。


  时间表如下:


13 点 42 分:发布了错误的代码

13 点 45 分:第一个警报到达(其他许多警报紧随而至)

14 点 00 分:查明问题出在 WAF 上

14 点 02 分:对 WAF 执行全局终止获得批准

14 点 07 分:终于执行终止命令(登录系统)

14 点 09 分:流量恢复正常

  Cloudflare 已相应改变了其系统和方法,以便将来这段响应时间有望从 27 分钟缩短至约 20 分钟(假设总是需要花一些时间来查明问题出在哪里。)


  至此,问题已查明,但 WAF 已下线,因此人们仍遇到问题。随后 Cloudflare 团队不得不搞清楚 WAF 里面出了什么差错,修复、检查然后重新启动。这项工作花了 53 分钟。


  博文中有一段描述了这整个过程:


  “由于形势的敏感性,我们从一座城市去掉付费客户的流量后,使用一小部分流量,在该城市既进行了负面测试(问自己‘这真是导致问题的那个特定变更吗?’),又进行了正面测试(验证回滚切实有效)。14 点 52 分,让我们觉得百分之百满意的是,我们已了解原因,部署了修复机制,并在全球重新启用 WAF。”


  除此之外没有更多的信息,不过博文后来确实提到“回滚计划需要运行两次完整的 WAF 创建,这花很长的时间。”


  休息时间


  博文还提到,Cloudflare 团队“因故障而很难访问我们自己的系统,旁路程序方面没有受过良好的培训”——不过目前不清楚这是否导致修复 WAF 方面出现延误。


  比如说,WAF 被下线后工程师花了多久才设法查出了导致问题的那一段代码?在 5 分钟内查明原因,然后运行了 47 分钟的测试?还是花了 47 分钟查明原因,然后运行了 5 分钟的测试?


  Cloudflare 在这篇非常详细而全面的博文中并没有给出答案,表明那是其难捱的一小时。你可以想象:它会调出列有问题发生前所做的所有变更的日志,删除那些变更,重建后测试。也许它这么做了。


  53 分钟这段时间够重建刚导致全球性故障的问题,然后重新投入使用吗?读者们有何想法?


  不管怎样,这就是它宕机的过程。值得赞扬的是,Cloudflare 还承认危机期间的沟通工作本可以做得更好。由于显而易见的原因,它的所有客户都吵着要求提供信息,但所有能给出答案的人都忙着修复故障。


  更糟糕的是,客户无法访问 Cloudflare 仪表板和 API, 因为他们通过受影响的 Cloudflare 边缘而进入——边缘受到了影响,所以客户实际上两眼抹黑。该公司计划通过这两个办法来解决这两个问题:一是为状态页面添加自动更新,二是在紧急情况下,有办法绕过平常的仪表板和 API 方法,以便人们可以获取信息。


  就是这样。目前尚不清楚这次故障对人们对于 Cloudflare 抱有的信心有多大影响。博文急切地指出,该公司整整六年来没出过一次全球性故障,当然不包括 Verizon 引发的问题。


  Cloudflare 坦诚而清晰地列出了一系列合理的改进——包括不对其超快速的全球更新系统发布非紧急更新,这在一定程度上有助于让客户吃下放心丸:Cloudflare 没有将越来越多的服务建立在不够好的代码上。


  运气好的话,依赖 Cloudflare 的互联网再过六年才会出现故障。


  Cloudflare 关于正则表达式回溯的问题阐述:


  想完全理解


1.png


  如何导致 CPU 资源耗尽,你需要对标准正则表达式引擎的工作原理有一番了解。关键部分.*(?:.*=.*)。(?:和匹配)是一个非捕获组(即括号内的表达式组合在一起作为单个表达式)。


  为了讨论此模式为什么导致 CPU 被耗尽,我们可以安全地忽略它,并将模式当作.*.*=.*来处理。如此简化后,模式显然看起来过于复杂; 但重要的是任何要求引擎“匹配任何后续部分”的“实际”表达式(比如我们的 WAF 规则中的复杂表达式)都可能导致灾难性的回溯。这就是原因。