原文:Understanding OpenStack Authentication: Keystone PKI
译注:该文对 Keystone 的验证机制写的真的是太赞了!文笔也很好。
OpenStack 最新的代码号为 Grizzly 的稳定发布版本革新了用户验证流程。你或许已经阅读了部分跟这新验证方案相关的文章。本文将尝试全面地对该新方案进行阐述,关注 Keystone 分发 token(令牌) 之后这些 token 如何被客户端用来签名他们的 API 调用而不是 Keystone 如何分发 token。客户端的这些请求稍后会由 OpenStack 的 API 端点(Endpoint)来验证。在本文中,你首先会看到为什么需要 token,接着知道 OpenStack 的 API 端点是如何利用 PKI(Public Key Infrastructure) token 来执行 token 验证而不用显式地调用 Keystone。让我们从客户端如何连接到 OpenStack 开始吧。
OpenStack、API 和 客户端
OpenStack 从哪开始从哪结束呢?用户会说,从云图形化界面或者命令行界面。然而,从架构角度来看,OpenStack 中间件(Middleware)结束于 API 端点:nova-api、glance-api 等等。这些端点均通过 http 的方式来暴露它们的 API。你可以通过各种不同的客户端连接到这些 API。客户端可由云服务供应商鉴证和部署在自己的基础设施上(如 Horizon 服务)或者安装在任何其他地方上(如 Nova 客户端 python-novaclient 可以安装在任何笔记本上,指向远程 nova-api 地址)。
这种(服务与客户端)分离隐含着一个重要的前提条件。因为客户端能够被安装在如何地方,所以它们是不能够被信任的;任何来自客户端的对任何 OpenStack 端点的请求都需要在其执行进一步的命令前被认证通过。
Token 是什么,我们为什么需要它们
在这种情况(指用户认证)下你需要做什么?是不是意味着你在每一个 API 请求中都要添加用户名和密码?没有这么简单。或者,把用户名和密码保存为系统变量?不安全。解决这个问题的方法就是使用 token。token 有一个非常显著的优点:它们是临时产生和短暂的。这意味着在客户端缓存它们要比保存用户名密码对来的安全。
通常来说,一个 token 是由 Keystone 分发给拥有合法用户名密码的用户的一个数据片段。就如我们前边提到的,跟 token 紧密相关的是它的失效时间(通常表现为小时甚至分钟)。用户可以缓存该 token 并将其注入到一个 OpenStack API 请求当中。然后 OpenStack API 端点从该请求中取出该 token 并将其跟 Keystone 认证后端进行验证,以确保请求的合法性。
现在将向大家介绍两种 token 认证方案:UUID(Universally Unique IDentifier) 和 PKI(Public Key Infrastructure)token 方案,以及它们的演变。
UUID token(Folsom 及以前 OpenStack 版本)
下图展示了 Keystone 是如何产生 token 以及 token 是如何被客户端用来签名 API 请求。
基于所提供的用户名/密码对(在这里我们假设它们是正确的):
- Keystone 会
- 产生一个 UUID token
- 将该 token 储存在其后端(backend)
- 将该 token 的一份复制(copy)发送给客户端
- 客户端缓存该 token
- 该 token 会在客户端的每一次 API 调用中跟随 API 一起发送到 OpenStack API 端点
- 对于用户的每一次请求,API 端点都会将该 token 发送至 Keystone 以验证其合法性
- Keystone 接受该 token 并将其跟认证后端进行匹配(检查 token 字符串、失效时间)
- Keystone 向 API 端点返回验证成功或者失败的消息
就如我们在上图中所能看到的,每一个用户在调用 API 端点时都需要与 Keystone 进行在线认证。想象一下成千上万的用户一起进行列举虚拟机、创建网络等操作的场景。这样的操作导致 Keystone 服务的过载。实际上,在生产过程中,Keystone 被证明在网络方面是过载最严重的 OpenStack 服务之一,但 Grizzly 很聪明地摆脱了这个问题。
现在进入 PKI token 部分。
PKI token(Grizzly 及后续 OpenStack 版本)
下图展示了在 Grizzly 发行版本中新方案是如何进行 token 验证的。
概括地说,拥有 PKI token 机制,Keystone 变成了一个认证授权中心(Certificate Authority, CA)。它利用签名密钥(signing key)和证书(certificate)去签名(不是加密
)用户 token。
另外,每一个 API 端点都拥有 Keystone 一下的一份复制:
- 签名证书
- 证书吊销列表
- CA 证书
API 端点利用这些来验证用户的请求,不需要跟 Keystone 进行直接交互。验证的内容是 Keystone 对用户 token 的签名和 Keystone 的吊销列表。API 端点利用这些数据来离线进行验证。
PKI token 背后
要在 Grizzly 中使用 PKI token,我们需要产生所需的所有密钥和证书。利用以下命令就可以做到:
1 | keystone-manage pki_setup |
该命令会产生以下文件:
- CA 私钥
1
openssl genrsa -out /etc/keystone/ssl/certs/cakey.pem 1024 -config /etc/keystone/ssl/certs/openssl.conf
- CA 证书
1
openssl req -new -x509 -extensions v3_ca -passin pass:None -key /etc/keystone/ssl/certs/cakey.pem -out /etc/keystone/ssl/certs/ca.pem -days 3650 -config /etc/keystone/ssl/certs/openssl.conf -subj /C=US/ST=Unset/L=Unset/O=Unset/CN=www.example.com
- 签名私钥
1
openssl genrsa -out /etc/keystone/ssl/private/signing_key.pem 1024 -config /etc/keystone/ssl/certs/openssl.conf
- 签名证书
1
2openssl req -key /etc/keystone/ssl/private/signing_key.pem -new -nodes -out /etc/keystone/ssl/certs/req.pem -config /etc/keystone/ssl/certs/openssl.conf -subj /C=US/ST=Unset/L=Unset/O=Unset/CN=www.example.com
openssl ca -batch -out /etc/keystone/ssl/certs/signing_cert.pem -config /etc/keystone/ssl/certs/openssl.conf -infiles /etc/keystone/ssl/certs/req.pem
Token 产生及格式
对于 PKI,Keystone 现在使用的是 CMS(Cryptographic Message Syntax)语法(译注:该语法可用于数字签名、加密)。Keystone 产生的 CMS token 包括如下数据:
- 服务目录(Service catalog)
- 用户角色(User roles)
- 元数据(Metadata)
一个输入数据的例子如下:
1 | { |
CMS token 仅仅是上述 CMS 格式中的 metadata 部分,由 Keystone 的签名密钥签名。它同时是一个冗长、表面看起来随机的字符串:
1 | MIIDsAYJKoZIhvcNAQcCoIIDoTCCA50CAQExCTAHBgUrDgMCGjCCAokGCSqGSIb3DQEHAaCCAnoEggJ2ew0KICAgICJhY2Nlc3MiOiB7DQogICAgICAgICJtZXRhZGF0YSI6IHsNCiAgICAgICAgICAgIC4uLi5tZXRhZGF0YSBnb2VzIGhlcmUuLi4uDQogICAgICAgIH0sDQogICAgICAgICJzZXJ2aWNlQ2F0YWxvZyI6IFsNCiAgICAgICAgICAgIC4uLi5lbmRwb2ludHMgZ29lcyBoZXJlLi4uLg0KICAgICAgICBdLA0KICAgICAgICAidG9rZW4iOiB7DQogICAgICAgICAgICAiZXhwaXJlcyI6ICIyMDEzLTA1LTI2VDA4OjUyOjUzWiIsDQogICAgICAgICAgICAiaWQiOiAicGxhY2Vob2xkZXIiLA0KICAgICAgICAgICAgImlzc3VlZF9hdCI6ICIyMDEzLTA1LTI1VDE4OjU5OjMzLjg0MTgxMSIsDQogICAgICAgICAgICAidGVuYW50Ijogew0KICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6IG51bGwsDQogICAgICAgICAgICAgICAgImVuYWJsZWQiOiB0cnVlLA0KICAgICAgICAgICAgICAgICJpZCI6ICI5MjVjMjNlYWZlMWI0NzYzOTMzZTA4YTRjNDE0M2YwOCIsDQogICAgICAgICAgICAgICAgIm5hbWUiOiAidXNlciINCiAgICAgICAgICAgIH0NCiAgICAgICAgfSwNCiAgICAgICAgInVzZXIiOiB7DQogICAgICAgICAgICAuLi4udXNlcmRhdGEgZ29lcyBoZXJlLi4uLg0KICAgICAgICB9DQogICAgfQ0KfQ0KMYH/MIH8AgEBMFwwVzELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVVuc2V0MQ4wDAYDVQQHEwVVbnNldDEOMAwGA1UEChMFVW5zZXQxGDAWBgNVBAMTD3d3dy5leGFtcGxlLmNvbQIBATAHBgUrDgMCGjANBgkqhkiG9w0BAQEFAASBgEh2P5cHMwelQyzB4dZ0FAjtp5ep4Id1RRs7oiD1lYrkahJwfuakBK7OGTwx26C+0IPPAGLEnin9Bx5Vm4cst/0+COTEh6qZfJFCLUDj5b4EF7r0iosFscpnfCuc8jGMobyfApz/dZqJnsk4lt1ahlNTpXQeVFxNK/ydKL+tzEjg |
产生这个字符串的命令如下:
1 | openssl cms -sign -signer /etc/keystone/ssl/certs/signing_cert.pem -inkey /etc/keystone/ssl/private/signing_key.pem -outform PEM -nosmimecap -nodetach -nocerts -noattr |
Token 验证以及失效
就如前文示意图所示的,PKI token 使得 OpenStack API 端点能够通过检验 Keystone 的签名来离线验证 token 的合法性。
有 3 件事需要验证:
- Token 的数字签名
- Token 的失效日期
- Token 是否在失效列表中(译注:Certificate Revocation Lists, CRL)
校验 token 签名
为了能够校验 token 的数字签名,每一个 API 端点都要从 Keystone 上获取证书。这些证书能够通过直接访问 Keystone 服务获得:
1 | curl http://[KEYSTONE IP]:35357/v2.0/certificates/signing |
如果 API 端点不能够从本地磁盘中找到这些证书,则会自动从 Keystone 下载。下述命令就是用来验证 token 的数字签名:
1 | openssl cms -verify -certfile /tmp/keystone-signing-nova/signing_cert.pem -CAfile /tmp/keystone-signing-nova/cacert.pem -inform PEM -nosmimecap -nodetach -nocerts -noattr < cms_token |
如果数字签名是合法的,上述命令返回包含在 CMS 中的 metadata。metadata 将会进一步被 API 端点使用。
检验 token 失效时间
从 CMS 中提取出来的一个 metadata 域就是 token 失效时间。该时间将会用来和当前时间进行比较。
处理已删除的 token
对 token 的删除是由 Keystone 通过将其放置到 token 失效列表中来执行的。默认地,这个 API 端点每隔一秒都会通过如下 URL 向 Keystone 拉取该列表:
1 | curl http://[KEYSTONE IP]:35357/v2.0/tokens/revoked |
失效列表的格式就是一个简单的 Json 文件:
1 | { |
其中,expires
域不需过多解释,id
看起来则有点神秘。它是对 CMS 用户 token 的 md5 哈希结果:md5(cms_token)
。API 端点也计算从用户请求中接受到的 token 的 md5 值,并查找该值是否存在于失效列表中。如果不存在,则该 token 就是合法的。
总结
OpenStack API 端点能够通过使用 PKI token 来进行用户 token 验证,而不需要显式地调用 Keystone。这能够提高 Keystone 的性能,在有大量的过载的用户请求时。一个警告就是,PKI 并不保证 token 的私密性。它只用来签名,而不是加密。如果你不想你的 token 被攻击,你应该使用 HTTPS 协议来保证对 API 端点的安全调用。