在分布式系统中,通常会有数量巨大的组件协作来完成某项具体的工作,协作的完成需要组件之间相互通信。在容器化部署的场景下,通信主要分为两类,容器实例之间,以及容器实例和外部系统之间的通信。无论是容器之间还是容器和外部系统之间,为了确保通信的安全,我们需要借助于安全机制来保护通信信息不被恶意攻击者获取。
阅读过笔者安全基础系列文章的同学应该对如何确保通信线路上的安全不陌生,如果你还有映像的话,我们介绍过如何使用对称加密以及非对称加密,哈希来确保通信的机密性和信息的防篡改。虽然笔者在介绍这些内容的时候并没有和具体的部署方式对应起来,大家大可粗略的认为所有的加密和安全手段在容器通信的场景中都是适用的。虽说笔者已经有系列文章详细介绍了加密相关的内容,但是对于容器部署的场景来说,咱们需要把前边介绍的安全基础内容带入到具体的场景中,来详细介绍容器间或者容器和外部系统之间的安全通信如何实现,这也是这篇文章的主要内容。
作为项目的技术负责人,架构师,我们经常需要为构建的云原生分布式系统配置证书(certificates),秘钥;特定情况下,我们也需要为Kubernetes集群配置安全认证中心(certificate authorities),为etcd配置证书来确保etcd和API server之间的通信安全等等。所说这些安全配置工作没有什么难度,但是相信有经验的读者经常需要面对:对着操作手册一顿执行后,发现结果不符合预期。背后的主要原因也不难理解,因为这些操作手册告诉我们的是WHAT(做),而我们可能根本不知道WHY和HOW,因此无论是手册本身缺少某个参数(或者由于版本问题未更新,某个命令和参数已经不适用)都会导致我们需要花费大量的时间来搞定。因此对通信安全所涉及到的所有组件有体系化的理解,以及知道这些组件是如何集成在一起提供安全防护是非常必要的。
安全通信这个概念虽然非常简单,但是笔者最近的实践经验显示,大部分关于技术的争论其实都是概念之争,因为技术术语都是经过抽象形成的缩略语,每个人的背景知识体系的差异,会导致端看似简单的概念,有着不小的理解偏差。特别是涉及到核心问题的时候,一般都是由于这些细小的理解偏差,导致经常会出现ppt上讲的很欢乐,一到具体落地就是各种理解不一致,争论。我了让本文后续的内容更加准确,咱先从定义什么是安全通信链路(secure connections)开始。
随着互联网和互联网应用的发展,特别是在中国内地,我们的日常生活已经彻底离不开”连接“。比如昨天晚上笔者带孩子和家人出去,路过新竹路保利时代附近看到有药店,妻子临时决定下车给孩子买抗过敏的药,谁知道这一去竟然花费了半个小时(预期在10分钟内,因为这个药很常见,也不是最近非常敏感的感冒药),买回来一问原因,智鹿大药房的网络出现故障,因此所有人必须排队,然后店员用本子记录每一笔销售记录,这就非常花费时间了。从这个场景可见一斑,我们对网络的依赖可能远远超过我们的认知。
咱们来看另外一个例子,虽然说大部分的银行现在都有自己的App,不过在5年前网银还占据主流,我们通过自己的浏览器打开银行的网络银行页面,哪个绿色的padlock让我们可以安心的输入自己的银行卡账号和密码,而不用太担心自己的账号被窃取。如果我们扒开这个绿色的padlock,支撑这种安全连接主要由两部分组成:
- 首先你需要非常小心的确认打开的网站是真的银行网络银行网站,这通过浏览器验证网站证书来实现。
- 其次当我们在打开的页面上输入银行卡和个人隐私信息的时候,数据在通信链路上进行了加密,来确保即便在中间节点有恶意攻击者在侦听通信数据,也无法对解析这密文从而让账户受损。
这种通信链路的安全通常由HTTPS协议提供,简单说HTTPS就是安全的HTTP协议。这种安全主要由传输层来保障,因此我们也习惯于把提供安全通信的手段称作TLS(Transport layer security)。说到这里,可能有同学会说,我还听说过SSL(secure socket layer),这个和HTTPS以及TLS有啥关系?本质上都是一回事,由于传输层是安全手段实施的地方,因此业界逐步用TLS来替换SSL这个术语。SSL规范是网景(Netscape)公司在1995年发布的通信安全规范,互联网工程任务组(IETF)基于网景公司的SSL v3.0在1999年发布了国际标准TLS v1.0,目前业界主要使用的是TLS v1.3。
因此无论是叫SSL还是TLS,本质上解决的两个通过网络连接的实体之间安全通信链路的问题。不过让人奔溃的是,经过了大约20多年的发展,还有相当一部分人继续把这种安全链路机制称作是SSL证书机制,即便是国际标准TLS已经更新了三个版本。你以为这就完了,实际上如果你从事的是安全领域,这两种叫法都不是太准确,准确来说,我们应该称这些证书为X.509证书,因为在传输层(transport layer)上使用的秘钥都是通过X.509证书来交换。总结一下笔者觉得如果你不是从事专业的安全领域,为了统一语言,大家还是称之为TLS会更加实用,也不会产生很多误解。
证书的格式由ITU(International Telecommunications Union)定义,也就是前文提到的X.509格式证书,大白话说就是一个数据结构,里边包含了证书持有者的identity信息,公钥信息等(希望读者还记得咱们前边关于非对称加密算法文章内容,公钥加密就是非对称加密,通常会产生一对秘钥:公钥和私钥)。如下图是一张证书的示意图,包含了证书持有者的信息,秘钥和过期时间等:

从上边的示意图我们可以清晰的看到证书包含的关键信息包含:
- 证书的持有者唯一标识符信息(云攀),我们通常把证书颁发给的实体(entity)也称作subject,而subject的名称一般在网络通信领域为domain name的形式,比如http://wwww.example.com 之类的,在真实使用场景中,证书还包含一个叫subject alternative name的字段,用来提供灵活性
- subject的公钥信息
- 证书的办法机构名称
- 证书的有效期,比如上图中的2099-11-28
咱们来说说这个公钥字段,非对称加密需要一对秘钥,公钥和私钥,如其名所指,公钥就是公钥的秘钥的意思,任何人都可以获取到网站的公钥。和公钥对应的是私钥,私钥信息应该保证机密性,只有owner才知道的信息。
我们通过openssl这样的工具创建证书的时候,私钥(private key)会首先被创建出来,然后基于私钥来计算创建公钥(public key),公钥和私钥虽然看起来很简单,但是使用场景和范围远超大家的想象:
- 通过公钥加密的信息,只有私钥才能解密,这解决了数据在传输过程中的机密性,也解决了秘钥交换的问题,因为公钥是可以通过公共渠道获取的信息
- 私钥可以用来对消息进行签名,这个签名信息可以用公开的公钥进行验证(verify),用来证明”我是我“的问题
咱们接下来请出爱丽斯女王和鲍勃领主,看看在王国内安全的传递八卦信息是如何借助于公钥和私钥提供的加密和签名能力的。女王可以通过自己的”宝箱“产生一对秘钥:公钥和私钥。然后将公钥让信使带给领主,这样领主就可以用女王的公钥来加密信息,然后将密文发送给女王,女王收到密文后,从保险箱中拿出自己的私钥,对密文进行解密,就获知了领主发送给自己的八卦信息。
但是这里有个问题,不知道大家是否觉察到?当女王让信使将自己的公钥穿越荒蛮之地带给领主的时候,领主如何能够相信这个公钥就是女王的?换句话说,如果信使换成自己公钥,或者半路上信使被斯塔克城堡主给活捉了,然后信使和斯塔克城堡主做了交易,信使带着斯塔克城堡主的公钥去见领主,这都是可能存在的场景。其实这种场景在我们日常生活中随处可见,过去几年网络上流行的段子”证明你妈是你妈“要解决的就是这个问题。
为了让领主能够验证收到的证书的确来自于女王,我们需要在通信双方之间引入大家都信任的第三方,通过第三方公证机构来为证书的可信背书,这在日常生活中就是派出所,而在安全中心中叫证书中心,证书颁发中心(certificate authority)。
证书中心,或者也叫CA,是一个被广泛信任的机构,主要的职责就是证书管理,包括证书的签发,验证等工作。CA在安全通信的场景中主要用来解决证书的可信任性问题,举个例子,当我们打开一个”自认为“是招商银行的网站,我们的浏览器收到对端的证书后,需要验证两个信息:第一个是证书是否颁发给这个URL;第二个是颁发证书的机构是否可信。
第一个问题比较容易解决,证书中的subject name和alternative subjec name可以用来验证访问的网站是否为钓鱼网站,而第二个问题就没有那么检查,我们如何信任颁发证书的CA?如果解决这问题的方案是再引入一个第三方,那么谁能为这个新引入的第三方可信背书呢?解决这种循环依赖的问题就是self-signed证书,一个特殊的X.509证书来为自己背书。如下图所示的证书链,女王的证书又鲍勃签发,鲍勃的证书由卡尔签发,卡尔通过self-signed类型的证书为自己背书:

有了卡洛提供的这种self-signed的自验证证书,我们就解决了这种先后鸡还是先有蛋的问题。通常情况下浏览器都默认安装了一批可信的证书,这些证书颁发自可信的CA,我们也把办法这些证书的机构称作为根CA(root CAs)。具体来说我们的浏览器会信任任何来自于这些根CA签发的证书,以及证书链,如果访问网站提供的证书不是来自于可信的证书中心CA,那么浏览器会显示错误,相信大家应该在浏览器上看到过类似的证书风险提醒信息。
如果我们希望自己的网站能够被公网用户访问,那么我们就需要提供一个来自于受信证书颁发机构签发的证书,比如阿里云就提供这样的服务,有了这个证书后,我们就可以在将证书上传到网站对应的组件上,对外提供安全的HTTPS连接服务。在分布式系统的设计中,不是所有组件都需要直接提供外部互联网用户的直接访问,比如Kubernetes的API Server和ETCD之间的安全线路,由于这是一种典型的内网两个组件之间的互操作,因此这个证书是否被浏览器验证可信并不重要,重要的是两个组件之间可以通过这个证书安全的通信。
无论是那种类型的证书,通常情况下我们需要请求CA帮助我们产生需要的证书问文件,证书通过CSR(Certificate Signing Request)请求发送给CA。CSR请求主要包含如下信息:
- 证书关联的公钥
- 证书签发的域名(domain name)
- 证书签发实体(entity)的企业基本信息,比如公司名称,公司的营业执照信息等
CSR请求会被发送给CA来生成X.509证书,从上边的信息的内容可以看到和证书中包含的信息相匹配。上边描述的信息和大家通过openssl生成证书的步骤略微不同,因为咱们通过openssl来创建秘钥对和证书是一步完成的。如果深入到openssl的秘钥创建过程,你会发现openssl只接受唯一的参数:私钥来创建证书。
背后的原理想必大家也能理解,因为公钥基于私钥计算而成,接着openssl会基于私钥来生成证书并签名,整个证书生成过程中不会使用公钥,因为公钥是用在对端对数据加密用,并且对端的公钥来自于从服务器上获得的证书中。有了对证书的初步了解之后,接下来咱们可以聊聊TLS(Transport Layer security)机制了。
传输层的安全链路的建立由客户端发起(client),而响应请求的一端叫做服务端(server),并且从技术原理的角度看,客户端和服务器端的概念只在传输层有效,因为在传输层之上,我们一般把通信的两端称作peers。具体来说客户端打开网络套接字socket发送请求给服务端,对于安全链路来说,服务端收到请求后会首先返回证书(certificate)。
客户端收到服务端返回的证书后,可以从中extract两个非常重要的信息:服务器端标识和公钥信息。server identify连同证书会被客户端进行验证,验证证书是否有效以及来自于可信机构颁发,以及证书中的domain name和访问的是否一致(预防钓鱼网站)。如果验证通过,客户端会用证书中的公钥来和服务器端建立后续沟通需要的对称加密秘钥(symmetirc key),至于为什么用对称加密秘钥,主要原因是对称加密的效率,具体的数据原理笔者会在后续安全基础类文章中介绍,大白话说就是对称加密基于bit操作而非对称加密基于数学计算,因此对称加密的效率要高很多。如下图所示:

大家在实际代码开发的过程中,肯定听说过skip verify机制,大白话说就是在客户端关闭对证书的验证,假设证书提供的信息可信,这主要是为了方便开发。但是在生产环境中千万不要开启这个配置,因为我们需要依赖于客户端的证书验证机制来确保服务端的真实有效。另外上图中也把服务端对客户端证书的verify表示出来了,特别是在金融领域,服务区需要知道持有这个账户的客户和声称的一致,因此需要验证客户端的证书来提供敏感账户信息,比如余额等。
文章的最后我们来整体看看上边描述的这些安全手段,对于容器实例间通信意味着什么?基于笔者过往的经验,以下最佳实践需要读者在自己的项目中尽量遵守:
- 咱们在安装K8S集群或者配置K8S集群的某些组件的时候,会涉及到证书的安装,大家要注意这些证书和我们所说的secure容器间的链路或者公网到容器实例间链路不同,不是一回事。
- 作为开发人员,我们编写代码的时候可能需要设置安全访问链路,比如支持HTTPS访问,那么就需要手动的来进行证书的管理和更新。不过笔者建议大家考虑使用服务网格的边车容器模式来替换。
证书在分布式系统中已经被当做基础设施来对待了,由于证书本身包含敏感信息,因此大家需要在自己的应用中,特别是K8S集群中安全的管理这类隐私数据。笔者在K8S集群系列文章中有关于secret管理的详细介绍,不过我们下篇文章还是会从容器安全的角度进行抽象和信息梳理。
整个基于证书的安全体系很健全,但是如果私钥的管理出现安全问题,我们所做的所有努力就将付之一炬。因此在大部分安全要求高的业务场景中,除了证书机制之外,我们还需从流程上保证证书定期更换。考虑到证书的数量以及更换可能导致的各种手动配置工作造成的风险,笔者强力建议采用自动的证书定期更换,以及使用像阿里云提供的KMS这样的托管秘钥管理服务来streamline这个过程。
好了,今天这篇文章就这么多了,下篇文章咱们来介绍在Kubernetes中如何安全的管理隐私信息,敬请期待!
注:在Kubernetes集群中,kublet和API Server之间需要证书的支持来进行https通信,并且使用的证书可以被很方便的替换;另外所有调用API Server提供的服务接口的客户端都需要证书来证明的identity。不过由于Kuberntes集群的特殊性,目前还不支持证书回收(certificate revocation)。我们需要使用RBAC来显示的声明某些账户对不可访问性。
