零信任网络

长期以来,主流的网络安全观念提倡根据某类与宿主机相关的特征,譬如机器所处的位置,或者机器的 IP 地址、子网等,把网络划分为不同的区域,不同的区域对应于不同风险级别和允许访问的网络资源权限,将安全防护措施集中部署在各个区域的边界之上,重点关注跨区域的网络流量。我们熟知的 VPN、DMZ、防火墙、内网、外网等概念,都可以说是因此而生的,这种安全模型今天被称为是基于边界的安全模型 (Perimeter-Based Security Model,后文简称“边界安全”)。

边界安全是完全合情合理的做法,在“ 架构安全性 ”的“ 保密 ”一节,笔者就强调过安全不可能是绝对的,我们必须在可用性和安全性之间权衡取舍,否则,一台关掉电源拔掉网线,完全不能对外提供服务的“服务器”无疑就是最为安全的。边界安全着重对经过网络区域边界的流量进行检查,对可信任区域(内网)内部机器之间的流量则给予直接信任或者至少是较为宽松的处理策略,减小了安全设施对整个应用系统复杂度的影响,以及网络传输性能的额外损耗,这当然是很合理的。不过,今天单纯的边界安全已不足以满足大规模微服务系统技术异构和节点膨胀的发展需要。边界安全的核心问题在于边界上的防御措施即使自身能做到永远滴水不漏牢不可破,也很难保证内网中它所尽力保护的某一台服务器不会成为“猪队友”,一旦“可信的”网络区域中的某台服务器被攻陷,那边界安全措施就成了马其诺防线,攻击者很快就能以一台机器为跳板,侵入到整个内网,这是边界安全基因决定的固有的缺陷,从边界安全被提出的第一天起,这就是已经预料到的问题。微服务时代,我们已经转变了开发观念,承认服务了总是会出错的,现在我们也必须转变安全观念,承认一定会有被攻陷的服务,为此,我们需要寻找到与之匹配的新的网络安全模型。

2010 年,Forrester Research的首席分析师 John Kindervag 提出了零信任安全模型 的概念(Zero-Trust Security Model,后文简称“零信任安全”),最初提出时是叫做“零信任架构”(Zero-Trust Architecture),这个概念当时并没有引发太大的关注,但随着微服务架构的日渐兴盛,越来越多的开发和运维人员注意到零信任安全模型与微服务所追求的安全目标是高度吻合的。

零信任安全模型的特征

零信任安全的中心思想是不应当以某种固有特征来自动信任任何流量,除非明确得到了能代表请求来源(不一定是人,更可能是另一台服务)的身份凭证,否则一律不会有默认的信任关系。在 2019 年,Google 发表了一篇在安全与研发领域里都备受关注的论文《BeyondProd: A New Approach to Cloud-Native Security》(BeyondCorp 和 BeyondProd 是谷歌最新一代安全框架的名字,从 2014 年起已连续发表了 6 篇关于 BeyondCorp 和 BeyondProd 的论文),此文中详细列举了传统的基于边界的网络安全模型与云原生时代下基于零信任网络的安全模型之间的差异,并描述了要完成边界安全模型到零信任安全模型的迁移所要实现的具体需求点,笔者将其翻译转述为如表 9-1 所示内容。

表 9-1 传统网络安全模型与云原生时代零信任模型对比

传统、边界安全模型 云原生、零信任安全模型 具体需求
基于防火墙等设施,认为边界内可信 服务到服务通信需认证,环境内的服务之间默认没有信任 保护网络边界(仍然有效);服务之间默认没有互信
用于特定的 IP 和硬件(机器) 资源利用率、重用、共享更好,包括 IP 和硬件 受信任的机器运行来源已知的代码
基于 IP 的身份 基于服务的身份 同上
服务运行在已知的、可预期的服务器上 服务可运行在环境中的任何地方,包括私有云/公有云混合部署 同上
安全相关的需求由应用来实现,每个应用单独实现 由基础设施来实现,基础设施中集成了共享的安全性要求。 集中策略实施点(Choke Points),一致地应用到所有服务
对服务如何构建、评审、实施的安全需求的约束力较弱 安全相关的需求一致地应用到所有服务 同上
安全组件的可观测性较弱 有安全策略及其是否生效的全局视图 同上
发布不标准,发布频率较低 标准化的构建和发布流程,每个微服务变更独立,变更更频繁 简单、自动、标准化的变更发布流程
工作负载通常作为虚拟机部署或部署到物理主机,并使用物理机或管理程序进行隔离 封装的工作负载及其进程在共享的操作系统中运行,并有管理平台提供的某种机制来进行隔离 在共享的操作系统的工作负载之间进行隔离

表 9-1 已经系统地阐述了零信任安全在微服务、云原生环境中的具体落地过程了,后续的整篇论文(除了介绍 Google 自己的实现框架外)就是以此为主线来展开论述的,但由于表格过于简单,论文原文写的较为分散晦涩,笔者对其中主要观点按照自己的理解转述如下:

  • ** 零信任网络不等同于放弃在边界上的保护设施** :虽然防火墙等位于网络边界的设施是属于边界安全而不是零信任安全的概念,但它仍然是一种提升安全性的有效且必要的做法。在微服务集群的前端部署防火墙,把内部服务节点间的流量与来自互联网的流量隔离开来,这种做法无论何时都是值得提倡的,至少能够让内部服务避开来自互联网未经授权流量的饱和攻击,如最典型的DDoS 拒绝服务攻击
  • ** 身份只来源于服务** :传统应用一般是部署在特定的服务器上的,这些机器的 IP、MAC 地址很少会发生变化,此时的系统的拓扑状态是相对静态的。基于这个前提,安全策略才会使用 IP 地址、主机名等作为身份标识符(Identifiers),无条件信任具有特性身份表示的服务。如今的微服务系统,尤其是云原生环境中的微服务系统,虚拟化基础设施已得到大范围应用,这使得服务所部署的 IP 地址、服务实例的数量随时都可能发生变化,因此,身份只能来源于服务本身所能够出示的身份凭证(通常是数字证书),而不再是服务所在的 IP 地址、主机名或者其它特征。
  • ** 服务之间也没有固有的信任关系** :这点决定了只有已知的、明确授权的调用者才能访问服务,阻止攻击者通过某个服务节点中的代码漏洞来越权调用到其他服务。如果某个服务节点被成功入侵,这一原则可阻止攻击者执行扩大其入侵范围,与微服务设计模式中使用断路器、舱壁隔离实现容错来避免雪崩效应类似,在安全方面也应当采用这种“互不信任”的模式来隔离入侵危害的影响范围。
  • ** 集中、共享的安全策略实施点** :这点与微服务的“分散治理”刚好相反,微服务提倡每个服务自己独立的负责自身所有的功能性与非功能性需求。而 Google 这个观点相当于为分散治理原则做了一个补充——分散治理,但涉及安全的非功能性需求(如身份管理、安全传输层、数据安全层)最好除外。一方面,要写出高度安全的代码极为不易,为此付出的精力甚至可能远高于业务逻辑本身,如果你有兴趣阅读 基于 Spring Cloud 的 Fenix's Bookstore 的源码 ,很容易就会发现在 Security 工程中的代码量是该项目所有微服务中最多的。另一方面,而且还是更重要的一个方面是让服务各自处理安全问题很容易会出现实现不一致或者出现漏洞时要反复修改多处地方,还有一些安全问题如果不立足于全局是很难彻底解决的,下一节面向于具体操作实践的“ 服务安全 ”中将会详细讲述。因此 Google 明确提出应该有集中式的“安全策略实施点”(原文中称之为 Choke Points),安全需求应该从微服务的应用代码下沉至云原生的基础设施里,这也契合其论文的标题“Cloud-Native Security”。
  • ** 受信的机器运行来源已知的代码** :限制了服务只能使用认证过的代码和配置,并且只能运行在认证过的环境中。分布式软件系统除了促使软件架构发生了重大变化之外,对软件的发布流程也有较大的改变,使其严重依赖持续集成与持续部署(Continuous Integration / Continuous Delivery,CI/CD)。从开发人员编写代码,到自动化测试,到自动集成,再到漏洞扫描,最后发布上线,这整套 CI/CD 流程被称作“软件供应链”(Software Supply Chain)。安全不仅仅局限于软件运行阶段,曾经有过XCodeGhost 风波这种针对软件供应链的有影响力的攻击事件,在编译阶段将恶意代码嵌入到软件当中,只要安装了此软件的用户就可能触发恶意代码,为此,零信任安全针对软件供应链的每一步都加入了安全控制策略。
  • ** 自动化、标准化的变更管理** :这点也是为何提倡通过基础设施而不是应用代码去实现安全功能的另一个重要理由。如果将安全放在应用上,由于应用本身的分散治理,这决定了安全也必然是难以统一和标准化的。做不到标准化就意味着做不到自动化,相反,一套独立于应用的安全基础设施,可以让运维人员轻松地了解基础设施变更对安全性的影响,并且可以在几乎不影响生产环境的情况下发布安全补丁程序。
  • ** 强隔离性的工作负载** :“工作负载”的概念贯穿了 Google 内部的 Borg 系统与后来 Kubernetes 系统,它是指在虚拟化技术支持下运行的一组能够协同提供服务的镜像。下一个部分介绍云原生基础设施时,笔者会详细介绍容器化,它仅仅是虚拟化的一个子集,容器比起传统虚拟机的隔离能力是有所降低的,这种设计对性能非常有利,却对安全相对不利,因此在强调安全性的应用里,会有专门关注强隔离性的容器运行工具出现。

Google 的实践探索

Google 认为零信任安全模型的最终目标是实现整个基础设施之上的自动化安全控制,服务所需的安全能力可以与服务自身一起,以相同方式自动进行伸缩扩展。对于程序来说,做到安全是日常,风险是例外(Secure by Default and Insecure by Exception);对于人类来说,做到袖手旁观是日常,主动干预是例外(Human Actions Should Be by Exception, Not Routine),这的确是很美好的愿景,只是这种“喊口号”式的目标在软件发展史上曾提出过多次,却一直难以真正达成,其中原因开篇就提过,安全不可能是绝对的,而是有成本的。很显然,零信任网络模型之所以在今天才真正严肃地讨论,并不是因为它本身有多么巧妙、有什么此前没有想到的好办法,而是受制于前文中提到的边界安全模型的“合理之处”,即“安全设施对整个应用系统复杂度的影响,以及网络传输性能的额外损耗”。

那到底零信任安全要实现这个目标的代价是什么?会有多大?笔者照 Google 论文所述来回答这个问题:为了保护服务集群内的代码与基础设施,Google 设计了一系列的内部工具,才最终得以实现前面所说的那些安全原则:

  • 为了在网络边界上保护内部服务免受 DDoS 攻击,设计了名为 Google Front End(名字意为“最终用户访问请求的终点”)的边缘代理,负责保证此后所有流量都在 TLS 之上传输,并自动将流量路由到适合的可用区域之中。
  • 为了强制身份只来源于服务,设计了名为 Application Layer Transport Security(应用层传输安全)的服务认证机制,这是一个用于双向认证和传输加密的系统,自动将服务与它的身份标识符绑定,使得所有服务间流量都不必再使用服务名称、主机 IP 来判断对方的身份。
  • 为了确保服务间不再有默认的信任关系,设计了 Service Access Policy(服务访问策略)来管理一个服务向另一个服务发起请求时所需提供的认证、鉴权和审计策略,并支持全局视角的访问控制与分析,以达成“集中、共享的安全策略实施点”这条原则。
  • 为了实现仅以受信的机器运行来源已知的代码,设计了名为 Binary Authorization(二进制授权)的部署时检查机制,确保在软件供应链的每一个阶段,都符合内部安全检查策略,并对此进行授权与鉴权。同时设计了名为 Host Integrity(宿主机完整性)的机器安全启动程序,在创建宿主机时自动验证包括 BIOS、BMC、Bootloader 和操作系统内核的数字签名。
  • 为了工作负载能够具有强隔离性,设计了名为gVisor的轻量级虚拟化方案,这个方案与此前由 Intel 发起的Kata Containers的思路异曲同工。目的都是解决容器共享操作系统内核而导致隔离性不足的安全缺陷,做法都是为每个容器提供了一个独立的虚拟 Linux 内核,譬如 gVisor 是用 Golang 实现了一个名为Sentry的能够提供传统操作系统内核的能力的进程,严格来说无论是 gVisor 还是 Kata Containers,尽管披着容器运行时的外衣,但本质上都是轻量级虚拟机。

作为一名普通的软件开发者,看完 Google 关于零信任安全的论文,或者听完笔者这些简要的转述,了解到即使 Google 也须花费如此庞大的精力才能做到零信任安全,最有可能的感受大概不是对零信任安全心生向往,而是准备对它挥手告别了。哪怕不需要开发、不需要购买,免费将上面 Google 开发的安全组件赠送于你,大多数开发团队恐怕也没有足够的运维能力。

在微服务时代以前,传统的的软件系统与研发模式的确是很难承受零信任安全模型的代价的,只有到了云原生时代,虚拟化的基础设施长足发展,能将复杂性隐藏于基础设施之内,开发者不需要为达成每一条安全原则而专门开发或引入可感知的安全设施;只有容器与虚拟化网络的性能足够高,可以弥补安全隔离与安全通信的额外损耗的前提下,零信任网络的安全模型才有它生根发芽的土壤。

零信任安全引入了比边界安全更细致更复杂安全措施的同时,也强调自动与透明的重要性,既要保证系统各个微服务之间能安全通信,同时也不削弱微服务架构本身的设计原则,譬如集中式的安全并不抵触于分散治理原则,安全机制并不影响服务的自动伸缩和有效的封装,等等。总而言之,只有零信任安全的成本在开发与运维上都是可接受的,它才不会变成仅仅具备理论可行性的“大饼”,不会给软件带来额外的负担。如何构建零信任网络安全是一个非常大而且比较前沿的话题,下一节,笔者将从实践角度出发,更具体更量化地展示零信任安全模型的价值与权衡。