Google系统架构解密:构建安全可靠的系统
上QQ阅读APP看书,第一时间看更新

第 1 章 安全性与可靠性的交集

撰写人:Adam Stubblefield、Massimiliano Poletto、Piotr Lewandowski、
David Huska 和 Betsy Beyer

1.1 从密码和电钻谈起

2012 年 9 月 27 日,Google 发布的一份内部公告导致了一系列的服务故障。最终,一把电钻排除了这些故障。

Google 有一个内部密码管理系统,用于员工存储密码,以及与不具备更高级身份验证机制的第三方服务进行机密共享。存储密码之一就是连接 Google 旧金山湾区各园区的公交车上的访客 Wi-Fi 密码。

在 9 月 27 日那天,运输团队通过电子邮件向数千名员工发布了 Wi-Fi 密码已更改的公告,最终导致流量远远超过了密码管理系统(该密码管理系统是几年前为一小部分系统管理员开发的)所能承受的峰值。

过载流量导致密码管理系统的主服务无法响应,因此负载均衡器将流量牵引到备份服务,结果备机也立即过载。此时,系统向值班工程师发出告警,而该工程师没有响应服务失败的经验:密码管理系统有良好的运行基础,并且在其存在的 5 年中从未遭受过停机。该工程师尝试重新启动服务,但不知道重新启动需要硬件安全模块(HSM)智能卡。

这些智能卡存储在全球各地的 Google 办事处的多个保险箱中,但不在值班工程师所在的纽约市。当服务无法重启时,该工程师联系了澳大利亚的一位同事,想取回智能卡。令人沮丧的是,澳大利亚的工程师无法打开保险箱,因为密码存储在离线的密码管理器中。幸运的是,加利福尼亚的另一位同事将该密码记录在了在线保险箱中,并且能够取回智能卡。但是,即使加利福尼亚的工程师将卡插入读卡器,该服务仍然因密码错误而无法重启:“密码无法加载任何保护此密钥的卡。”

澳大利亚的工程师认为应该用蛮力解决他们的安全问题,因此用起了电钻。一小时后,保险箱打开了,但即便是刚取回的卡也触发了相同的错误消息。

团队花了一个多小时才意识到,智能卡读取器上的绿灯实际上并不表示该卡已正确插入。当工程师将卡翻转过来时,服务重启成功,故障排除了。

可靠性和安全性都是一个真正值得信赖的系统的关键组成部分,但要构建既可靠又安全的系统是很困难的。尽管对可靠性和安全性的要求具有许多共性,但设计它们时也有不同的注意事项。忽视可靠性和安全性之间的微妙关系很容易引起意外的结果。密码管理系统的故障是由可靠性问题(失效的负载均衡和过载保护策略)触发的,而提高系统安全性的多种措施使恢复密码管理系统变得复杂。

安全与隐私的交集

安全与隐私是密切相关的概念。系统为了尊重用户隐私,必须从根本上是安全的,并且在对手面前表现得与预期相同。同样,一个完善的安全系统如果不尊重用户隐私,就不能满足许多用户的需求。虽然本书关注的是安全性,但描述的一些方法通常也用于实现隐私保护。

1.2 可靠性与安全性:设计注意事项

在设计可靠性和安全性时,必须考虑不同的风险。可靠性面对的主要风险往往是非恶意的,例如软件更新失败或物理设备故障,而安全风险来自主动尝试利用系统漏洞的对手。在设计可靠性时,你会假设某些地方在某些时候会出错;在设计安全性时,你必须假设有人随时随地蓄意搞破坏。

因此,不同的系统在故障响应的设计上也完全不同。在没有破坏者的情况下,系统通常会在故障发生时默认放过(或默认打开),例如电子锁默认在断电时保持打开状态,允许人们安全进出。在故障发生时默认放过/打开的设计可能导致明显的安全漏洞。为了防御可能利用电源故障的人,可以将门设计为在故障发生时默认保护,并在不通电时保持关闭状态。

可靠性与安全性的权衡:冗余

在设计可靠性时,通常需要向系统添加冗余。举例来说,许多电子锁在断电时会默认保护,但会允许使用物理钥匙。同样,火灾逃生通道为紧急情况提供了冗余的出口。尽管冗余提高了可靠性,但也增加了攻击面。对抗方只需在一条路径上找到漏洞即可成功。

 

可靠性与安全性的权衡:事件管理

攻击者的存在还会影响协作方法以及事件发生时响应者可以使用的信息。可靠性受益于拥有多角度的响应者,它们可以协助快速发现问题的根本原因并解决问题。相比之下,通常在处理安全事件时更希望将解决问题的人员最少化,使对手无法获得相关消息。在安全事件中,以按需共享的原则来分享信息。同样,海量的系统日志有助于为事件响应提供信息支撑,并缩短恢复所需事件的时间,但根据记录的内容不同,日志也可能成为攻击者的重要目标。

1.3 机密性、完整性、可用性

安全性和可靠性都与系统的机密性、完整性和可用性有关,但它们针对这些因素的考虑角度不同。两者之间的关键差异在于是否存在恶意的对手。可靠的系统一定不会出现违背机密性的问题,例如出错的聊天系统可能存在错发、乱码或丢失消息的情况。此外,安全的系统必须防止恶意访问、篡改或破坏机密数据。通过下面的例子来看看可靠性问题是如何导致安全问题的。

 在传统定义中,机密性、完整性和可用性一直被视为安全系统的基本属性,叫作 CIA1 三要素。尽管很多模型基于 CIA 扩展了安全属性,但 CIA 三要素长期以来仍受欢迎。虽然首字母缩写一样,但这个概念与美国中央情报局没有任何关系。

1此处的 CIA 是指 confidentiality(机密性)、integrity(完整性)和 availability(可用性)。——编者注

1.3.1 机密性

在航空业中,一个明显的机密性问题是一键通按钮卡在了发声位置。在一些有据可查的案例中,卡住的麦克风按钮广播了飞行员之间的私人对话,这明显违反了机密性。在这种情况下,并不存在恶意攻击者,而是硬件可靠性缺陷导致设备在非飞行员预期的情况下传递了信息。

1.3.2 完整性

同样,数据完整性被破坏也不一定涉及攻击者。2015 年,Google 网站可靠性工程师注意到,端到端加密的完整性检查在一些数据块上出现了失败的情况。处理数据的某些机器存在无法纠正的内存错误,因此网站可靠性工程师决定改写程序,通过按位取反(0 和 1 互换)操作彻底检查计算每个数据版本的完整性。这样一来,他们才能知道结果是否与原始完整性检查的值相匹配。事实证明,所有错误实际上都是按位取反导致的,后续网站可靠性工程师恢复了所有数据。有趣的是,这是一个在完整性故障中采用安全技术来恢复的实际案例。(Google 的存储系统使用的是非加密的端到端完整性检查,但因为一些其他因素,SRE 团队没有发现按位取反的故障。)

1.3.3 可用性

最后一点,可用性显然既关乎可靠性,又关乎安全性。攻击者可能会利用系统漏洞,使系统停止运行或阻止已授权用户的使用。或者,攻击者可能会控制分布在世界各地的大量设备来发动经典的分布式拒绝服务(distributed denial-of-service, DDoS)攻击,利用这些设备向受害者发送大量请求。

拒绝服务(denial-of-service, DoS)攻击很有趣,这是因为它横跨可靠性领域和安全性领域。从受害人的视角来看,很难区分恶意攻击流量与设计缺陷或合理上升带来的峰值流量。举个例子,2018 年的一次软件更新导致一些 Google Home 设备和 Chromecast 设备在调整时钟时产生了巨大的网络流量,进而导致 Google 的中央时间服务出现了非预期的流量峰值。与之类似,重大的突发新闻或事件会使得数百万人发出几乎相同的查询,这看起来很像传统的应用级 DDoS 攻击。如图 1-1 所示,2019 年 10 月的一个深夜,旧金山湾区发生了 4.5 级地震,该地区的 Google 基础设施服务接收到大量查询流量。

图 1-1:2019 年 10 月 14 日,旧金山湾区发生 4.5 级地震时,该地的 Google 基础设施服务接收到的 Web 流量(以每秒 HTTP 请求量计算)

1.4 可靠性与安全性:共性

与许多系统特性不同,可靠性和安全性是系统设计中的突出属性。这两点都很难在事后再加固,因此理想情况下,在设计的初期阶段就应该将两者都考虑在内。系统变更过程中很容易无意中影响到这两点,因此需要在整个系统生命周期中持续关注和测试它们。在复杂的系统中,很多组件会影响到安全性和可靠性,一个看似不经意的更新可能对整个系统的安全性或可靠性造成影响,而这种影响在事故发生前可能并不明显。让我们通过更多细节来研究这些共同点。

1.4.1 隐形

当系统运行一切正常时,可靠性和安全性几乎是隐形的,但可靠性和安全性团队的目标之一是赢得并保持客户和合作伙伴的信任。无论是出现问题时,还是在进展顺利的情况下,良好的沟通都是这种信任的坚实基础。沟通中应该开诚布公、细致入微,并且避免套话和行话,这非常重要。

不幸的是,在没有紧急情况的时候,可靠性和安全性的隐形意味着它们通常被视为可以减少或推迟也不会立即造成严重后果的成本,但它们失效时带来的损失可能很严重。据媒体报道,可能因为数据泄露事件的出现,2017 年威瑞森收购雅虎的互联网业务的价格降低了 3.5 亿美元。同一年,断电事件导致达美航空的核心系统停机,造成近 700 架航班取消和数千次的航班延误,使达美当天的航班吞吐量减少了将近 60%。

1.4.2 评估

因为实现绝对可靠性或安全性的想法是不切实际的,所以可以从实际风险出发来评估负面事件带来的成本,以及预防这些事件的前期成本和机会成本。但是,对于可靠性和安全性,应该以不同的方式衡量负面事件的可能性。我们可以推导出系统组成的可靠性,并根据所需的错误预算 2 来计划工程工作。其中至少一部分可以这样,因为我们可以假设各个组件之间的故障是无关的。而组合的安全性就更难评估了。通过分析系统的设计和实现,可以提供一定程度的保证。攻防演练(以攻击者的角度开展测试)也可以用于评估系统对特定攻击类型的抵抗力、攻击检测机制的有效性以及受到攻击的潜在后果。

2有关错误预算的更多信息,请参阅《SRE:Google 运维解密》中的第 3 章。

1.4.3 简洁性

使系统设计尽可能精简,是提高对系统可靠性和安全性的评估能力的最佳方法之一。较简单的设计缩小了攻击面,减少了系统非预期交互的可能性,并使系统更容易被理解。在紧急情况下,可理解性尤其有用,它可以帮助响应者快速解决问题并减少平均修复时间(MTTR)。第 6 章将详细阐释该主题,并讨论相关策略,例如将攻击面最小化,以及将安全不变量的权责划分到简单的、独立运行的子系统中,以便于独立地推理。

1.4.4 演变

无论初始设计多么简洁和优雅,系统极少情况下会保持长久不变。添加新功能、改变规模以及底层基础设施的演进都会让系统变得复杂。在安全方面,跟进不断发展的攻击手法和新攻击者的需求也会使系统变得复杂。此外,满足市场需求的压力也会导致系统开发人员和维护人员走捷径,从而积攒技术债务。第 7 章讨论了其中一些挑战。

复杂性通常会在不经意间积累起来,这可能会导致系统到达临界点。在这种情况下,一个看起来无关紧要的微小变更就会对系统的可靠性或安全性产生重大影响。Debian GNU/ Linux 版本的 OpenSSL 库就出现过一个因微小变更而导致重大故障的案例:2006 年引入的一个错误,在将近两年后才被发现。一位开源研发人员注意到 Valgrind(一款调试内存问题的标准工具)报告了一个问题:内存使用前未进行初始化。为了消除警告,研发人员删除了两行代码。不幸的是,这导致 OpenSSL 的伪随机数生成器只使用进程 ID 作为随机种子,而在 Debian 系统中,该 ID 当时默认在 1~32 768 范围内取值,因此可以很轻易地通过暴力破解获取加密密钥。

Google 也同样无法幸免。例如,2018 年 10 月,YouTube 在全球范围内停机了超过一小时,起因是通用日志库里的一个小变更。该变更的目的是提升事件日志的细粒度,在其作者和指定的代码审阅者看来都没有问题,并且通过了所有测试。然而,其开发者完全没有意识到在 YouTube 的业务规模下会产生的影响:在生产环境的负载下,这个变更导致 YouTube 的服务器快速地耗尽内存并崩溃。当用户的流量转移到其他正常的服务器时,级联故障导致整个服务都停止了。

1.4.5 弹性

当然,一个内存利用率问题不应该引起全局的服务中断。系统应在非预期情况下具有弹性。从可靠性角度来看,这种情况通常是因预期外的高负载或者组件故障而导致的。负载是系统请求数量和平均开销组合而成的,因此可以通过减少负载量(减少执行)或者减少每个请求的平均开销(执行成本更低)来实现弹性。为了定位组件故障,系统设计应该包括冗余和不同的故障域,以便通过重新路由请求来限制降低故障的影响。第 8 章将进一步讨论这些话题,第 10 章将特别深入探讨 DoS 缓解措施。

无论一个系统的各个组件具有多大的弹性,一旦变得足够复杂,就难以保证整个系统不受伤害,但可以通过纵深防御和故障域来解决这个问题。纵深防御是多种防御机制的集合(有时候是冗余)。独立故障域限制了故障的“爆炸半径”,因此也提高了可靠性。良好的系统设计会限制攻击者利用沦陷的主机或者盗取的凭证进行横向移动或是提权,进而影响系统的其他部分。

你可以通过划分权限或者限制凭证的可用范围来实现不同的故障域。举个例子,Google 内部基础设施所支持的凭证明确地限定了地理区域。这类特性可以限制攻击者向其他区域服务器横向移动的能力(如果已经掌控了一个区域的服务器)。

对敏感数据使用独立的加密层是深度防御的另一种常见机制。举个例子,即使磁盘提供了设备级加密,在应用程序层也加密数据通常是个好主意。这样,假设攻击者获取通过物理方式访问存储设备的方法,即使驱动控制器中的加密算法实现有缺陷,也不足以危及受保护数据的机密性。

虽然目前为止引用的例子都来自外部攻击者,但内部恶意人员的潜在威胁也是需要考虑的。尽管内部人员可能比第一次窃取员工凭证的外部攻击者更了解潜在的突破点,但在实践中这两种情况通常没有太大区别。最小特权原则可以缓解内部的威胁。它规定用户只能在指定时间范围内拥有工作所需的最小权限集。举个例子,UNIX 的 sudo 机制支持细粒度的策略,用以指定哪些用户可以作为什么角色来运行哪些命令。

在 Google,我们还使用多方授权来确保敏感操作由特定的员工组来审批。这种多方机制既能防御内部恶意人员,又能降低人为错误的风险(可靠性故障的常见原因)。最小特权和多方授权不是新鲜事物,它们已经为很多非计算机的场景所应用,大到核导弹发射井,小到银行金库。第 5 章将深入讨论这些概念。

1.4.6 从设计到生产

将一个可靠设计转化为完整部署的生产系统的过程中,要一直考虑安全性和可靠性。从代码开发开始,就有机会通过代码审阅来发现潜在的安全性和可靠性问题,甚至可以通过使用公共框架和库来防止产生问题。第 12 章探讨了这方面的技术。

在部署系统之前,可以用测试来确保它的功能在正常情况下和边缘情况(通常会影响可靠性和安全性)下都能正常工作。无论是使用负载测试来了解系统在大量查询下的行为,或是使用模糊测试来探索潜在的非预期输入后的表现,抑或使用特定的测试来确认加密库是否会泄露信息,测试都在其中发挥着关键作用,以确保实际构建的系统符合设计意图。第 13 章深入介绍了这些方法。

最后,一些实际部署代码的方法(参见第 14 章)可以控制安全性和可靠性的风险。例如,金丝雀测试和缓慢部署可以防止系统崩溃并波及所有用户。同样,如果部署系统只接受经过合理审阅的代码,则将有助于降低内部人员将恶意二进制文件发布至生产环境的风险。

1.4.7 调查系统和日志

到目前为止,我们都将重点放在了防止可靠性和安全故障的设计原则和实现方法上。不幸的是,想要达到完美的可靠性和安全性,成本通常很高,因此不切实际。我们必须假设预防机制有失败的可能,因此需要制订计划来检测故障并从故障中恢复。

将如第 15 章所述,完善的日志记录是检测和防备故障的基础。一般来说,日志越完整详细就越好,但这个准则有些注意事项。在足够大的规模下,大量日志会带来很大的成本,并且会给分析日志造成困难。前文中提到的 YouTube 案例说明日志也会导致可靠性问题。安全日志还带来了额外的挑战:日志通常不应该包含认证凭证或个人身份信息(personally identifiable information, PII)等敏感信息,以免日志本身成为使攻击者感兴趣的目标。

1.4.8 危机响应

在紧急情况下,团队必须快速而顺利地协作,否则可能立即出现不良后果。在最坏的情况下,一个事件可能在几分钟内毁掉一家企业。例如,2014 年,一名攻击者接管了代码托管服务 Code Spaces 的管理端工具,然后删除了该服务的所有数据,包括所有备份,导致该服务在几小时内完全不可用。这些情况下都需要及时响应,而精心排练过的协作和良好的事件管理就显得尤为重要了。

组织危机响应非常有挑战性,所以最好是在紧急情况发生之前就制订好响应计划。发现事件是需要时间的。在任何情况下,响应人员都是在压力和时间紧迫的情况下操作的,并且(至少在刚开始时)对情况的认知有限。如果组织规模较大,同时事件需要 24 小时响应或者跨时区协作、跨团队维护和班次间事件管理交接,就会导致操作进一步的复杂化。安全事件通常会存在矛盾:一方面想让各个关键方都参与进来;另一方面,通常法律和监管要求限制信息只能在必要知晓时才能共享。而且,最初的安全事件可能只是冰山一角。调查可能超出公司范围,或者涉及执法机构。

在危机期间,至关重要的是一个清晰的指挥链以及一套坚实的检查清单、行动手册和协议。将如第 16 章和第 17 章所述,Google 已经将危机响应转化成名为“Google 事件管理”(Incident Management at Google, IMAG)的程序,包含处理各类事件的标准化方法(无论是系统中断,还是自然灾害),并组织有效的响应。IMAG 是基于美国突发事件指挥系统(Incident Command System, ICS)模型建立的,这是一种用于指挥、控制和协调多个政府机构的应急人员的标准化方法。

当没有面临持续事件的压力时,应急人员通常会有大段的空白时间,没有行动。在这些时间段里,团队需要保持个人技能和行动的敏锐性,并改进流程和基础设施,为下一次的应急做准备。Google 的灾难恢复测试系统(Disaster Recovery Testing program, DiRT)会定期模拟各种内部系统故障,迫使团队去应付这些场景。频繁地攻击性安全演习能测试防御能力,并有助于识别新的漏洞。Google 甚至在小事件中也使用 IMAG,这进一步促使我们定期使用应急工具和流程。

1.4.9 恢复

从安全事故中恢复,通常需要为了修复漏洞而打补丁。直觉上,你希望使用非常可靠、经常运行的机制让该过程尽快流转。然而,快速推送变更的能力是一把双刃剑:虽然此功能可以帮助快速关闭漏洞,但它也可能引入错误或性能问题,从而造成大量破坏。如果漏洞广为人知或风险严重,则快速推送补丁的压力更大。推动修复程序的节奏究竟是快还是慢(缓慢推进时确保无副作用,但漏洞就存在被利用的风险),最终归结为风险评估和业务决策。例如,通过降低某些性能或增加资源使用量来修复严重漏洞或许是可以接受的。

这样的选择强调了需要可靠的恢复机制,使我们能够在不影响可靠性的情况下快速推出必要的变更,并且还可以在潜在问题导致大范围故障之前发现这些问题。例如,稳健的舰队恢复系统需要可靠地表示每台机器的当前和所需状态,还需要提供备份,以确保状态永远不会回滚到过时或不安全的版本。第 9 章将介绍该方法及同类方案,第 18 章将讨论在事件发生后如何实际恢复系统。

1.5 小结

安全性和可靠性有很多共同之处——它们都是所有信息系统的固有属性,最初很容易成为换取更快速度的代价,而事后则需要高昂的修复成本。本书旨在帮助你在系统增长过程中,尽早解决与安全性和可靠性相关的不可避免的挑战。除了工程方面的努力,每个组织都必须了解有助于构建安全性和可靠性文化(参见第 21 章)的角色和责任(参见第 20 章),以便坚持可持续的实践。通过分享我们的经验和教训,我们希望你能够在系统生命周期的早期充分地采用这些原则,从而避免在以后的道路上付出更大的代价。

我们写这本书时考虑到了广大读者,目标是无论项目处于什么阶段或范围,读者都会发现它是相关的。阅读本书时,请牢记项目的风险概况,这是因为运营股票交易所或政见交流平台的风险与运营动物保护区网站的风险截然不同。下一章将详细讨论攻击者的类别及其可能的动机。