keystone三种认证方式:UUID、PKI、Fernet


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

1.UUID认证原理

以UUID形式返回token,每次都需要到keystone认证,造成性能瓶颈

当用户需要进行操作时(比如访问nova创建虚拟机),用户拿着有效的用户名/密码先去keystone认证,keystone返回给用户一个token(即UUID)。之后用户进行其他操作(如访问nova),先出示这个token给nova-api,nova收到请求后,就用这个token去向keystone进行请求验证。keystone通过比对token,以及检查token的有效期,判断token的有效性,最后返回给nova结果

缺陷:每次请求都要经过keystone进行验证,造成性能瓶颈

1.用户输入用户名密码,发送给keystone

2.Keystone验证用户名密码,并且生成token(UUID),发送给客户端

3.客户端缓存UUID token

4.客户端发送具体的执行请求(nova boot)和UUID给keystone

5.Keystone从http请求中获取token,并检查token是否有效

6.Token有效,处理请求,并返回客户端请求结果

7.Token失效,拒绝客户端请求,返回401

UUID方式源码分析:

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

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

生成的样例:144d8a99a42447379ac37f78bf0ef608

2.PKI认证原理

以非对称加密方式对token进行加密,同时在认证初期生成密钥对(公钥和私钥)和证书

当用户以token到相应服务组件请求服务时,该组件通过证书对token进行解密获取用户信息,以此与失效列表进行比对

也需要每次请求keystone获取失效列表,但对性能影响较低

在keystone初始化时,keystone生成了CA的公钥CA.pem和私钥CA.key,同时keystone产生了自己的公钥keystone.pub和私钥keystone.key,然后将keystone.pub进行CA的签名,生成keystone.pem

当用户拿着用户名/密码去keystone认证后,keystone将用户的基本信息通过keystone.key进行加密,并将密文作为token返还给用户。当用户拿着token发送请求时(例如访问nova),nova拿到用户token时,通过事先拿到keystone的证书keystone.pem(这一过程只需要进行一次)进行解密,获取用户信息

对于用户的token,还需进行token的合法时间,以及token还是否存在进行判断。所以当nova每一次拿到token后还需向keystone询问一次token的失败列表,检查token是否失效。这一过程对于keystone的负载还是相当轻的,所以PKI还是有效解决了keystone性能瓶颈的问题

小结:OpenStack服务中的每一个API Endpoint都有一份keystone签发的证书,失效列表和根证书。API不用在直接去keystone认证token是否合法,只需要根据keystone的证书和失效列表就可以确定token是否合法。但是这里还是会有每次都需要请求keystone去获取失效列表的操作,不可避免

PKI的流程,首先需要使用 keystone-manage pki_setup命令生成CA及相关的令牌机制,其代码如下所示:

    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))   #DEFAULT_TOKEN_DIGEST_ALGORITHM=sha256

其中,‘token_data’是获取的user、role、endpoint、catlog等信息集合,而最重要的语句是cms使用签名生成token的过程:cms_sign_token,使用默认的sha256方式加密,处理过程使用process,进行数据的读取、处理,

           process = subprocess.Popen(['openssl', 'cms', '-sign',

                                 '-signer', signing_cert_file_name,

                                 '-inkey', signing_key_file_name,

                                 '-outform', 'PEM',

                                 '-nosmimecap', '-nodetach',

                                 '-nocerts', '-noattr',

                                 '-md', message_digest, ],

                                stdin=subprocess.PIPE,

                                stdout=subprocess.PIPE,

                                stderr=subprocess.PIPE,

                                close_fds=True)

最后output, err = process.communicate(data) 生成Token-id,这个过程涉及到openssl相关的加密技术

CMS token一般都超过1600个字节,样例:

3.Fernet认证原理

Fernet采用对称加密方式,以key的方式认证token,不需要存储于数据库

采用轮换key的方式,当key认证失败即token过了有效期,简化了token过期验证

1.user在客户端输入用户名密码,发送给keystone

2.Keystone验证用户名密码,并且生成token(UUID),发送给客户端

3.客户端缓存token(UUID)

4.客户端发送具体的执行请求给openstack API

5.OpenStack API向 keystone请求token认证

6.Keystone从http请求中获取token,并检查token是否有效

7.Token有效,处理请求,并返回openstack api请求结果

8.Token失效,拒绝客户端请求,返回401

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

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

该token 的大小一般在 200 多 Byte 左右,样例:

gAAAAABWfX8riU57aj0tkWdoIL6UdbViV-632pv0rw4zk9igCZXgC-sKwhVuVb-wyMVC9e5TFc7uPfKwNlT6cnzLalb3Hj0K3bc1X9ZXhde9C2ghsSfVuudMhfR8rThNBnh55RzOB8YTyBnl9MoQ XBO5UIFvC7wLTh_2klihb6hKuUqB6Sj3i_8

简要叙述一下fernet采用 Key Rotation 更换密钥的原理,默认的轮换长度是3,当以keystone-manage fernet-setup生成密钥时,会看到0、1两个索引表征,这分别是什么意思呢?

在此,需要提一下三个概念:

primary key(主密钥)有且只有一个,名为为x,当前用于加密解密token

secondary key(次次密钥)有x-1个,从Primary退役下来的,用于解密当初它加密过的token

staged key(次密钥)有且只有一个,命名为0,准备下一个rotation时变为Primary key,可以解密token

那么上述0 表示的是staged key,1 表示的是primary key,

primary key相比较另外两种key,它的索引最高,并且可以加密、也可以解密;

staged key 相较于secondary key,它更有机会变为primary key

AES256加密token,SHA256 HMAC验证完整性,

只要Keystone具有访问这些key的权限,token就不需要在keystone数据库中存储

fernet的数据性能最好,原因是它不需要后端持久化操作(采用 Key Rotation定期 更换密钥,只要Keystone具有访问这些key的权限,更新后的token就不需要在keystone数据库中存储,缓解了数据库负载压力),并且token的认证,使用的是密钥进行解密,能够直接得出token Data的信息,从而进行token的过期认证。它的失败原因,只可能是token过期了,或者是token放到了cache缓存中,但是已经被回收了。归根到底,还是token过期了

参考链接: