相当复杂的现代 Web 服务大多数都不是由单体应用提供。为了处理复制的操作,应用程序通常被分解成许多服务,分别处理业务逻辑或数据存储的不同部分。这些服务可能部署在不同的机器甚或是不同的数据中心。在 CloudFlare ,随着服务的增加,应用程序之间安全通信的需求也在增长。他们需要一种简单、可维护的方法来确保 CloudFlare 内部服务之间的所有通信都始终处于安全保护之下。因此,他们基于已知且可靠的协议构建了一个这样的系统。该系统基于一个“公钥基础设施(public key infrastructure,缩写为 PKI)”,使用了内部托管的认证中心(CA)。近日,CloudFlare 系统工程师 Nick Sullivan介绍了私有PKI 构建过程和内部使用方式。
他们的方法是所有的新服务都使用一种同加密协议——传输层安全(TLS)协议——保护服务间通信。这是一种很自然的选择:HTTPS 中的“S”就是TLS,它是Web 加密的基础。而且,现代Web 服务和API 均以TLS 作为应用层加密事实上的标准。它可以与RESTful 服务无缝集成,并获得了 Kyoto Tycoon 、 PostgreSQL 和 Go 标准库的支持。另外,Nick 在先前发表的一篇文章中讨论过,未经身份验证的加密可能遭受中间人攻击。也就是说,加密但不做身份验证无法在传输中保护数据。为了连接安全,每个连接方都必须向另一方提供身份标识。公钥加密技术提供了许多种信任机制,包括 PGP 的“信任网络( web of trust )”和 HTTPS 的公钥基础设施模型。由于更易于使用和部署,他们选择了 PKI,由它和 TLS 一起提供可信任的通信。
PKI 借助数字证书和公钥加密技术提供可信任的网络身份。通常,证书就是一个包含如下身份信息的文件:
- 证书所有组织的信息
- 公钥
- 证书颁发组织的信息
- 证书颁发组织授予的权限,如证书有效期、适用的主机名、用途等
- 使用证书颁发组织私钥创建的数字签名
每个公钥都有一个对应的私钥,后者在证书所有者的管控之下,可以用于对数据进行数字签名,验证器可以使用证书中的公钥对数据进行验证。如果证书本身包含第三方认证中心的数字签名,那么只要验证器信任该第三方,就可以确保证书是合法的。有时候,证书是由中介认证中心签名,而中介认证中心的证书又是由不同的认证中心签名。在这种情况下,证书验证器会沿着这条链一直找到它信任的证书。对于认证中心而言,信任链模型非常有用,它允许我们将根证书的私钥离线存储,只为中介证书签名。中介认证中心的正式存在时间较短, 可以为端点证书签名。
这与 Web 上 HTTPS 使用的系统相同。但对于不需要通过浏览器访问的内部服务,就没有必要通过第三方认证中心。也就是说,受信任证书不必由 Globalsign 、 Comodo 、 Verisign 或其它认证中心颁发,它们可以由你自己的 CA 颁发。
创建自己的认证中心(CA)
为了创建一个可以轻松获取和操作证书的内部认证中心,他们使用了自己开源的 PKI 工具箱 CFSSL 。该工具具有运行一个认证中心所需的全部功能。虽然 CFSSL 是为运行内部 CA 而创建,但它足够健壮,可以用于公开的受信任 CA。实际上, Let’s Encrypt 项目就使用 CFSSL 作为 CA 基础设施的一个核心部件。
运行认证中心需要一个 CA 证书和相应的私钥。后者是极其敏感的数据。任何知道私钥的人都可以充当 CA 颁发证书。因此,私钥的保护至关重要。CFSSL 支持以下三种私钥保护模式:
- “硬件安全模块( Hardware Security Module ,缩写为 HSM)
- Red October
- 纯文本
接下来,我们将沿着 Nick 的思路看下如何使用纯文本私钥快速配置一个内部 CA。
生成 CA 证书和私钥
创建一个包含如下组织基本信息的文件csr_ca.json
:
{ "CN": "My Awesome CA", "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "US", "L": "San Francisco", "O": "My Awesome Company", "OU": "CA Services", "ST": "California" } ] }
执行下面的命令:
$ cfssl gencert -initca csr_ca.json | cfssljson -bare ca
该命令会生成运行 CA 所必需的文件ca-key.pem
(私钥)和ca.pem
(证书),还会生成ca.csr
(证书签名请求),用于交叉签名或重新签名。
配置证书生成策略,并启动 CA 服务
配置证书生成策略,让 CA 软件知道颁发什么样的证书。下面是一个简单的示例:
config_ca.json { "signing": { "default": { "auth_key": "key1", "expiry": "8760h", "usages": [ "signing", "key encipherment", "server auth" ] } }, "auth_keys": { "key1": { "key": <16 byte hex API key here>, "type": "standard" } } }
该策略指定了证书有效期(1 年)、用途(服务器验证等)以及一个随机生成的私有验证密钥。该密钥可以防止未经授权的机构请求证书。
执行下面的命令,启动 CA 服务:
$ cfssl serve -ca-key ca-key.pem -ca ca.pem -config config_ca.json
证书生成与签名
截止目前,基于 CFSSL 的 CA 已经配置完成,不妨假设它运行在服务器“ca1.mysite.com”上。该 CA 如何颁发证书呢?CFSSL 提供了两个命令:gencert
和sign
。gencert
将自动处理整个证书生成过程。该过程需要两个文件,一个告诉 CFSSL 本地客户端 CA 的位置以及如何验证请求,另一个为 CSR 配置信息,用于填充 CSR。下面是为一个为数据库服务 db1.mysite.com 创建证书的例子:
config_client.json
{ "signing": { "default": { "auth_key": "key1", "remote": "caserver" } }, "auth_keys": { "key1": { "key": <16 byte hex API key here>, "type": "standard" } }, "remotes": { "caserver": “ca1.mysite.com:8888" } } {1}
csr_client.json
{ "hosts": [ "db1.mysite.com" ], "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "US", "L": "San Francisco", "O": “My Awesome Company", "OU": “Data Services", "ST": "California" } ] }
有了这两个文件就可以使用下面的命令为数据库服务器 db1.mysite.com 创建证书了:
$ cfssl gencert -config config_client.json csr_client.json | cfssljson -bare db
前面已经提到过,该命令会生成三个文件,其中db-key.pem
、db.pem
和db.csr
,其中db.csr
可以再次提交给 CA,使用sign
命令重新签名:
$ cfssl sign -config config_client.json db.csr | cfssljson -bare db-new
该命令会生成新证书db-new.pem
。这两个命令使私有 PKI 搭建变得非常容易和便利。
使用 PKI
为应用程序生成证书和密钥有两种方式:集中式和分布式。前者是指预先配置好一台中央服务器,由它创建所有的证书并发送给每台应用程序服务器;后者是指由应用服务器创建自己的私钥,并向验证中心发送证书申请。按照 Nick 的说法,在第一种方式中,中央服务器管理复杂,而且向应用服务器传输私钥会引入不必要的风险。相比之下,第二种方式可以按需申请证书,非常易于扩展。
确立服务之间的信任关系
浏览器通过检查证书签名以及根据“主题备用名称(Subject Alternative Names,缩写为 SAN)”列表检查主机名来验证网站证书。这种显式检查有用,但可能会出现不正常情况。另一种使服务相互信任的方式是基于单服务 CA 的隐式检查,其思想很简单:每组服务使用不同的 CA。比如,由数据库 CA 颁发所有数据库的证书,由 API 服务器 CA 颁发所有 API 服务器的证书。
当这些服务彼此间使用相互 TLS 验证进行通信时,将信任关系配置为:
- API 服务器只信任 DB CA
- DB 服务器只信任 API CA
配置完成后,A 类型的服务将只能和 B 类型的服务通信。下图描述了两个应用程序如何使用相互 TLS 验证确立相互信任关系:
如上图,API 服务器信任 DB CA(红色)。因此,它只接受由 DB CA(带红丝带)签名的证书。反之,数据库服务器只接受由 API CA(带橙丝带)签名的证书。为了建立一个受信任连接,双方互相发送一个“密钥共享(key share)”,并用它们的私钥签名。密钥共享合并到一起创建一个会话密钥,会话双方用它加密数据。
将 PKI 用于远程服务
内部 PKI 非常灵活,可以用于向集成到 PKI 所在网络的第三方发放证书。例如,CloudFlare 有一个名为 Railgun 的服务,可以用于优化 CloudFlare 与源服务器的连接。Railgun 与 CloudFlare 之间的通信就是使用 CloudFlare 认证中心颁发的证书进行加密与身份验证。这可以确保数据传输安全。
小结
实现应用程序层数据安全是确保分布式系统架构安全的重要一步,但只有在有一个强大的 PKI 时才能实现真正有效的安全防护。
感谢徐川对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群)。
评论