Token 是什么

通俗的讲,token 是用户的一种凭证,需拿正确的用户名/密码向 Keystone 申请才能得到。如果用户每次都采用用户名/密码访问 OpenStack API,容易泄露用户信息,带来安全隐患。所以 OpenStack 要求用户访问其 API 前,必须先获取 token,然后用 token 作为用户凭据访问 OpenStack API。 


四种 Token 的由来

D 版本时,仅有 UUID 类型的 Token,UUID token 简单易用,却容易给 Keystone 带来性能问题,从图一的步骤 4 可看出,每当 OpenStack API 收到用户请求,都需要向 Keystone 验证该 token 是否有效。随着集群规模的扩大,Keystone 需处理大量验证 token 的请求,在高并发下容易出现性能问题。

于是 PKI(Public Key Infrastructrue) token 在 G 版本运用而生,和 UUID 相比,PKI token 携带更多用户信息的同时还附上了数字签名,以支持本地认证,从而避免了步骤 4。因为 PKI token 携带了更多的信息,这些信息就包括 service catalog,随着 OpenStack 的 Region 数增多,service catalog 携带的 endpoint 数量越多,PKI token 也相应增大,很容易超出 HTTP Server 允许的最大 HTTP Header(默认为 8 KB),导致 HTTP 请求失败。

顾名思义,PKIZ token 就是 PKI token 的压缩版,但压缩效果有限,无法良好的处理 token size 过大问题。

前三种 token 都会持久性存于数据库,与日俱增积累的大量 token 引起数据库性能下降,所以用户需经常清理数据库的 token。为了避免该问题,社区提出了 Fernet token,它携带了少量的用户信息,大小约为 255 Byte,采用了对称加密,无需存于数据库中。


UUID

UUID token 是长度固定为 32 Byte 的随机字符串,由 uuid.uuid4().hex 生成。

def _get_token_id(self, token_data):
return uuid.uuid4().hex

但是因 UUID token 不携带其它信息,OpenStack API 收到该 token 后,既不能判断该 token 是否有效,更无法得知该 token 携带的用户信息,所以需经图一步骤 4 向 Keystone 校验 token,并获用户相关的信息。其样例如下:

144d8a99a42447379ac37f78bf0ef608

UUID token 简单美观,不携带其它信息,因此 Keystone 必须实现 token 的存储和认证,随着集群的规模增大,Keystone 将成为性能瓶颈。


PKI

在阐述 PKI(Public Key Infrastruction) token 前,让我们简单的回顾公开密钥加密(public-key cryptography)数字签名。公开密钥加密,也称为非对称加密(asymmetric cryptography,加密密钥和解密密钥不相同),在这种密码学方法中,需要一对密钥,分别为公钥(Public Key)和私钥(Private Key),公钥是公开的,私钥是非公开的,需用户妥善保管。如果把加密和解密的流程当做函数 C(x) 和 D(x),P 和 S 分别代表公钥和私钥,对明文 A 和密文 B 而言,数学的角度上有以下公式:

B = C(A, S)
A = D(B, P)

其中加密函数 C(x), 解密函数 D(x) 以及公钥 P 均是公开的。采用公钥加密的密文只能用私钥解密,采用私钥加密的密文只能用公钥解密。非对称加密广泛运用在安全领域,诸如常见的 HTTPS,SSH 登录等。

数字签名又称为公钥数字签名,首先采用 Hash 函数对消息生成摘要,摘要经私钥加密后称为数字签名。接收方用公钥解密该数字签名,并与接收消息生成的摘要做对比,如果二者一致,便可以确认该消息的完整性和真实性。

PKI 的本质就是基于数字签名,Keystone 用私钥对 token 进行数字签名,各个 API server 用公钥在本地验证该 token。相关代码简化如下:

def _get_token_id(self, token_data):
try:
token_json = jsonutils.dumps(token_data, cls=utils.PKIEncoder)
token_id = str(cms.cms_sign_token(token_json,
CONF.signing.certfile,
CONF.signing.keyfile))
return token_id

其中 cms.cms_sign_token 调用 openssl cms --sign 对 token_data 进行签名,token_data 的样式如下:

{
"token": {
"methods": [ "password" ],
"roles": [{"id": "5642056d336b4c2a894882425ce22a86", "name": "admin"}],
"expires_at": "2015-12-25T09:57:28.404275Z",
"project": {
"domain": { "id": "default", "name": "Default"},
"id": "144d8a99a42447379ac37f78bf0ef608", "name": "admin"},
"catalog": [
{
"endpoints": [
{
"region_id": "RegionOne",
"url": "http://controller:5000/v2.0",
"region": "RegionOne",
"interface": "public",
"id": "3837de623efd4af799e050d4d8d1f307"
},
......
]}],
"extras": {},
"user": {
"domain": {"id": "default", "name": "Default"},
"id": "1552d60a042e4a2caa07ea7ae6aa2f09", "name": "admin"},
"audit_ids": ["ZCvZW2TtTgiaAsVA8qmc3A"],
"issued_at": "2015-12-25T08:57:28.404304Z"
}
}

token_data 经 cms.cms_sign_token 签名生成的 token_id 如下,共 1932 Byte:

MIIKoZIhvcNAQcCoIIFljCCBZICAQExDTALBglghkgBZQMEAgEwggPzBgkqhkiG9w0B
......
rhr0acV3bMKzmqvViHf-fPVnLDMJajOWSuhimqfLZHRdr+ck0WVQosB6+M6iAvrEF7v

PKIZ

PKIZ 在 PKI 的基础上做了压缩处理,但是压缩的效果极其有限,一般情况下,压缩后的大小为 PKI token 的 90 % 左右,所以 PKIZ 不能友好的解决 token size 太大问题。

def _get_token_id(self, token_data):
try:
token_json = jsonutils.dumps(token_data, cls=utils.PKIEncoder)
token_id = str(cms.pkiz_sign(token_json,
CONF.signing.certfile,
CONF.signing.keyfile))
return token_id

其中 cms.pkiz_sign() 中的以下代码调用 zlib 对签名后的消息进行压缩级别为 6 的压缩。

compressed = zlib.compress(token_id, compression_level=6)

PKIZ token 样例如下,共 1645 Byte,比 PKI token 减小 14.86 %:

PKIZ_eJytVcuOozgU3fMVs49aTXhUN0vAQEHFJiRg8IVHgn5OnA149JVaunNS3NYjoSU
......
W4fRaxrbNtinojheVICXYrEk0oPX6TSnP71IYj2e3nm4MLy7S84PtIPDz4_03IsOb2Q=

Fernet

用户可能会碰上这么一个问题,当集群运行较长一段时间后,访问其 API 会变得奇慢无比,究其原因在于 Keystone 数据库存储了大量的 token 导致性能太差,解决的办法是经常清理 token。为了避免上述问题,社区提出了Fernet token,它采用 cryptography 对称加密库(symmetric cryptography,加密密钥和解密密钥相同) 加密 token,具体由 AES-CBC 加密和散列函数 SHA256 签名。Fernet 是专为 API token 设计的一种轻量级安全消息格式,不需要存储于数据库,减少了磁盘的 IO,带来了一定的性能提升。为了提高安全性,需要采用 Key Rotation 更换密钥。

def create_token(self, user_id, expires_at, audit_ids, methods=None,
domain_id=None, project_id=None, trust_id=None,
federated_info=None):
"""Given a set of payload attributes, generate a Fernet token.""" if trust_id:
version = TrustScopedPayload.version
payload = TrustScopedPayload.assemble(
user_id,
methods,
project_id,
expires_at,
audit_ids,
trust_id) ... versioned_payload = (version,) + payload
serialized_payload = msgpack.packb(versioned_payload)
token = self.pack(serialized_payload) return token

以上代码表明,token 包含了 user_id,project_id,domain_id,methods,expires_at 等信息,重要的是,它没有 service_catalog,所以 region 的数量并不影响它的大小。self.pack() 最终调用如下代码对上述信息加密:

def crypto(self):
keys = utils.load_keys() if not keys:
raise exception.KeysNotFound() fernet_instances = [fernet.Fernet(key) for key in utils.load_keys()]
return fernet.MultiFernet(fernet_instances)

该 token 的大小一般在 200 多 Byte 左右,本例样式如下,大小为 186 Byte:

gAAAAABWfX8riU57aj0tkWdoIL6UdbViV-632pv0rw4zk9igCZXgC-sKwhVuVb-wyMVC9e5TFc
7uPfKwNlT6cnzLalb3Hj0K3bc1X9ZXhde9C2ghsSfVuudMhfR8rThNBnh55RzOB8YTyBnl9MoQ
XBO5UIFvC7wLTh_2klihb6hKuUqB6Sj3i_8

如何选择 Token

Token 类型 UUID PKI PKIZ Fernet
大小 32 Byte KB 级别 KB 级别 约 255 Byte
支持本地认证 不支持 支持 支持 不支持
Keystone 负载
存储于数据库
携带信息 user, catalog 等 user, catalog 等 user 等
涉及加密方式 非对称加密 非对称加密 对称加密(AES)
是否压缩
版本支持 D G J K

Token 类型的选择涉及多个因素,包括 Keystone server 的负载、region 数量、安全因素、维护成本以及 token 本身的成熟度。region 的数量影响 PKI/PKIZ token 的大小,从安全的角度上看,UUID 无需维护密钥,PKI 需要妥善保管 Keystone server 上的私钥,Fernet 需要周期性的更换密钥,因此从安全、维护成本和成熟度上看,UUID > PKI/PKIZ > Fernet 如果:

    • Keystone server 负载低,region 少于 3 个,采用 UUID token。
    • Keystone server 负载高,region 少于 3 个,采用 PKI/PKIZ token。
    • Keystone server 负载低,region 大与或等于 3 个,采用 UUID token。
    • Keystone server 负载高,region 大于或等于 3 个,K 版本及以上可考虑采用 Fernet token。

http://wsfdl.com/openstack/2015/12/26/%E7%90%86%E8%A7%A3Keystone%E7%9A%84%E5%9B%9B%E7%A7%8DToken.html

2个问题:

PKI既然支持本地验证为何还有存到DB中?

uuid既然有有效时间,为何不自动在DB中删除?

(转)理解Keystone的四种Token的更多相关文章

  1. 快速理解VirtualBox的四种网络连接方式

    VirtualBox中有4中网络连接方式: NAT Bridged Adapter Internal Host-only Adapter VMWare中有三种,其实他跟VMWare 的网络连接方式都是 ...

  2. 理解Android的四种启动模式

    一:前言 四种模式分别为standard, singleTop, singleTask, singleInstance.自己应该明确一个概念先,single到底要single什么.每一个应用app都有 ...

  3. 深入理解SQL的四种连接-左外连接、右外连接、内连接、全连接(转)

    1.内联接(典型的联接运算,使用像 =  或 <> 之类的比较运算符).包括相等联接和自然联接.     内联接使用比较运算符根据每个表共有的列的值匹配两个表中的行.例如,检索 stude ...

  4. 【转】深入理解SQL的四种连接-左外连接、右外连接、内连接、全连接

    [原文]:http://www.jb51.net/article/39432.htm 1.内联接(典型的联接运算,使用像 =  或 <> 之类的比较运算符).包括相等联接和自然联接.    ...

  5. 深入理解SQL的四种连接,左外连接,右外连接,内连接,全连接

    1.内联接(典型的联接运算,使用像 =  或 <> 之类的比较运算符).包括相等联接和自然联接.     内联接使用比较运算符根据每个表共有的列的值匹配两个表中的行.例如,检索 stude ...

  6. 转【深入理解SQL的四种连接-左外连接、右外连接、内连接、全连接】

    转自:https://www.jb51.net/article/39432.htm 1.内联接(典型的联接运算,使用像 =  或 <> 之类的比较运算符).包括相等联接和自然联接.     ...

  7. 深入理解SQL的四种连接

    SQL标准 select table1.column,table2.column from table1 [inner | left | right | full ] join table2 on t ...

  8. CSS系列,三栏布局的四种方法

    三栏布局.两栏布局都是我们在平时项目里经常使用的,今天我们来玩一下三栏布局的四种写法,以及它的使用场景. 所谓三栏布局就是指页面分为左中右三部分然后对中间一部分做自适应的一种布局方式. 1.绝对定位法 ...

  9. Keystone几种token生成的方式分析

    从Keystone的配置文件中,我们可见,Token的提供者目前支持四种. Token Provider:UUID, PKI, PKIZ, or Fernet 结合源码及官方文档,我们用一个表格来阐述 ...

随机推荐

  1. JmsTemplate 发送方式

    ---恢复内容开始--- 背景: 原来我准备是setDefaultDestinationName 设置队列的名称 发现 系统运行后  创建 的并不是队列 ,而是Topic  , 自己看下源码,发现在创 ...

  2. Brouwer fixed-point theorem

    w https://en.wikipedia.org/wiki/Brouwer_fixed-point_theorem https://zh.wikipedia.org/wiki/布劳威尔不动点定理 ...

  3. python作用域和JavaScript作用域

    JavaScript 一.JavaScript中无块级作用域 一个大括号一个作用域,就属于块级作用域,在Java和c#才存在块级作用域 function Main(){ if(1==1){ var n ...

  4. 线程锁、threading.local(flask源码中用的到)、线程池、生产者消费者模型

    一.线程锁 线程安全,多线程操作时,内部会让所有线程排队处理.如:list/dict/Queue 线程不安全 + 人(锁) => 排队处理 1.RLock/Lock:一次放一个 a.创建10个线 ...

  5. ReentrantLock VS synchronized

    ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票.定时锁等候和可中断锁等候的一些特性. 此外,它还提供了在激烈争用情况 ...

  6. 面试10大算法汇总+常见题目解答(Java)

    原文地址:http://www.lilongdream.com/2014/04/10/94.html(为转载+整理) 以下从Java的角度总结了面试常见的算法和数据结构:字符串,链表,树,图,排序,递 ...

  7. 江卓尔与比特币增发,谣言or先知-千氪

    最近,很多圈内人都在讨论比特币是否应该增发,但等等,比特币真的会增发吗?到底是真有增发计划还是某些人别有用心地在散布谣言? 那么消息是从哪里出来的?北京时间 2 月 10 日晚,莱比特矿池创始人江卓尔 ...

  8. STL之空间配置器

    在前面很多随笔里都有提到new对象是先分配内存然后初始化对象,主要是对operator new和placement new的使用 在SGI STL中内存的分配和初始化是分开的,分配内存是使用类模板,模 ...

  9. (转)fiddler使用简介--其一

    原文地址:https://www.cnblogs.com/miantest/p/7289694.html Fiddler基础知识 Fiddler是强大的抓包工具,它的原理是以web代理服务器的形式进行 ...

  10. 百度地图sn计算方法说明

    官方说明[有状态码的数值意义查询 和 python 转换源码]: http://lbsyun.baidu.com/index.php?title=lbscloud/api/appendix