Keystone Token Format

在最新的devstack上玩耍的时候,token ID的长度发生了变化,并不是正常UUID的32字节长度

[lihui@openstack ~]$ openstack token issue
+------------+---------------------------------------------------------------------------------------------------------------------------------------------+
| Field      | Value                                                                                                                                       |
+------------+---------------------------------------------------------------------------------------------------------------------------------------------+
| expires    | 2017-04-07T19:22:44+0000                                                                                                                    |
| id         | gAAAAABY59j0KSkY4bs7niTxOXb-4Ap8vz2iFPuKGvO8HxjJdeupoZ3xTkM-                                                                                |
|            | RhYP9JM8aLQWZOM2DtY9p7b0G6YCehRq7trqpWvIK51QMTfqB0d2yC3O4yeXQ29s6lzdPHW6pHQDUnZ6uh6JZHEXXZb42G1uBn17NnKVwJwRHfXPHGKGeKTi04s                 |
| project_id | f26ed882adff490b8f65ef3653e97b39                                                                                                            |
| user_id    | d7d21e3aee1d4bc5b51a5265952ca481                                                                                                            |
+------------+---------------------------------------------------------------------------------------------------------------------------------------------+

简单看了下长度,有180多

>>> token = 'gAAAAABY59j0KSkY4bs7niTxOXb-4Ap8vz2iFPuKGvO8HxjJdeupoZ3xTkM-RhYP9JM8aLQWZOM2DtY9p7b0G6YCehRq7trqpWvIK51QMTfqB0d2yC3O4yeXQ29s6lzdPHW6pHQDUnZ6uh6JZHEXXZb42G1uBn17NnKVwJwRHfXPHGKGeKTi04s'
>>> print len(token)
183

具体token id的生成是哪种format,在keystone的配置文件/etc/keystone/keystone.conf里可以找到如下:

[token]
driver = sql
provider = fernet

可以看到我这里配置的format并不是uuid,而是fernet

假如也是通过devstack来搭建,可以直接修改keystone源码来达到配置的目的,具体位置在devstack/lib/keystone:

# Select Keystone's token provider (and format)
# Choose from 'uuid', 'pki', 'pkiz', or 'fernet'
KEYSTONE_TOKEN_FORMAT=${KEYSTONE_TOKEN_FORMAT:-fernet}
KEYSTONE_TOKEN_FORMAT=$(echo ${KEYSTONE_TOKEN_FORMAT} | tr '[:upper:]' '[:lower:]')

从这里看到这里有4中format可配置,KEYSTONE_TOKEN_FORMAT默认这里配置的是fernet,假如想恢复成自己习惯的UUID格式的token,直接将fernet改成uuid即可

下面简单描述下这四种format

【uuid】

这里是随机生成的32byte字符串,就像这样

>>> import uuid
>>> uuid.uuid4().hex
'8cc622b1ec654c228fc2d22afd3ad7e8'
>>> uuid.uuid4().hex
'9830fab1620d4a5ca9f0fd1ba18c03eb'
>>> uuid.uuid4().hex
'7bd7f1e261434bba9ae4be27d7ec54b2'
>>> uuid.uuid4().hex
'6e0a55f1a882469fbcf6909a06b65b0a'

这里的token不携带任何其它信息,在其它API收到了token之后,无法确认这个token到底是否还有效,比如一般token的持续时间设置24小时,假如这个token已经过期了,除此之外这个token到底用得对不对,比如和对应的tenant是否一致,这都是需要keystone来进行交互验证,也就是需要持久化保存token,当存储过多的时候,肯定会导致性能不好了

【fernet】

由于uuid方式只是简单的32字节长度随机字符串,没有加密方式,fernet就是针对这种情况做的改进,根据社区的说法:Fernet token,它采用 cryptography 对称加密库(symmetric cryptography,加密密钥和解密密钥相同) 加密 token,具体由 AES-CBC 加密和散列函数 SHA256 签名。Fernet 是专为 API token 设计的一种轻量级安全消息格式,不需要存储于数据库,减少了磁盘的 IO,带来了一定的性能提升。为了提高安全性,需要采用 Key Rotation 更换密钥。

说得天花乱坠,直接看源码,在keystone/token/providers/fernet/token_formatters.py

    def create_token(self, user_id, expires_at, audit_ids, methods=None,
                     domain_id=None, project_id=None, trust_id=None,
                     federated_info=None, access_token_id=None):
        """Given a set of payload attributes, generate a Fernet token."""
        for payload_class in PAYLOAD_CLASSES:
            if payload_class.create_arguments_apply(
                    project_id=project_id, domain_id=domain_id,
                    trust_id=trust_id, federated_info=federated_info,
                    access_token_id=access_token_id):
                break

        version = payload_class.version
        payload = payload_class.assemble(
            user_id, methods, project_id, domain_id, expires_at, audit_ids,
            trust_id, federated_info, access_token_id
        )

        versioned_payload = (version,) + payload
        serialized_payload = msgpack.packb(versioned_payload)
        token = self.pack(serialized_payload)

        # NOTE(lbragstad): We should warn against Fernet tokens that are over
        # 255 characters in length. This is mostly due to persisting the tokens
        # in a backend store of some kind that might have a limit of 255
        # characters. Even though Keystone isn't storing a Fernet token
        # anywhere, we can't say it isn't being stored somewhere else with
        # those kind of backend constraints.
        if len(token) > 255:
            LOG.info('Fernet token created with length of %d '
                     'characters, which exceeds 255 characters',
                     len(token))

        return token

token里面塞入了payload,而这里payload里封装了一堆有用的东西,加一个version,self.pack()可以看做是加密起来,返回一个token

【pki】

这种format用devstack安装貌似中途有的地方没法安装,就直接列出官方说法,PKI的本质就是基于数字签名,Keystone用私钥对token进行数字签名,各个API server用公钥在本地验证该token

这种token感觉比较坑,网上查的有的说快2000字符长度,有的说4000度字符长度,我这里暂时不验证了,总之长度负载太重了,用起来是在不方便

【pkiz】

这种format实际上是在pkt的基础上做了压缩处理,压缩之后,长度负载大概是pki的90%左右,这个感觉可用性还是不太好,都不太人性化

总之,对于如何选择Token,网上有做过总结

Token类型 UUID PKI PKIZ Fernet
大小 32Byte KB级别 KB级别 约255Byte
本地认证 不支持 支持 支持 不支持
存储在数据库
携带信息 user,catalog等 user,catalog等 user等
加密方式 非对称加密 非对称加密 对称加密
是否压缩
版本支持 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。

目前我devstack环境里貌似就只有uuid和fernet两种format,其它两种可自行查询摸索

发表评论