Keystone认证和授权流程分析




本文基于mitaka版本keystone、nova源码进行分析

密码管理

创建用户、更新用户密码时会保存加密后的密码到keystone数据库password表中,密码采用passlib.hash库进行sha512算法加盐计算多轮hash后保存,加密后密码类似:

$6$rounds=10000$Lu4gJYSkJijDptF5$AUhTPBXXWZXAwLHyDQFYbZ5uo282V5SX.s4OK4/I6oNm88XiCqUvomTOUqLtN0ZV7WhfZHcalNP7ClIh0QAXk.

使用$符号进行分割,对应格式为:$6$rounds=rounds$salt$checksum

第一段的$6$表示是passlib中的sha512-crypt hashes算法,第二段中的rounds=10000表示经过多少次sha512 hash运算,第三段的salt是加密用的盐,明文保存,第四段是密码真正的hash加密密文。其中salt是由passlib随机生成的16位字符串,每个用户的密码都不同,或者说每次生成密文时都随机产生一次,保证所有用户salt都不一样,这样即使被拖库后基于彩虹表的密码反推破解方式就非常困难,每个用户的密码都要根据salt字符串生成一份彩虹表,然后加上加密rounds数量10000次,极大的增加了破解难度,从而达到破解密码成本比账号本身价值高的目的,所谓得不偿失,也就不太乐意去干了。另外也加salt方式也可以有效避免拖库后被拿去撞库攻击其他网站。参考:http://blog.jobbole.com/61872/

参考资料:https://passlib.readthedocs.io/en/stable/lib/passlib.hash.sha256_crypt.html

上面的链接是sha256的,sha512算法跟sha256除了加密后的字符数量长一倍(更安全)之外,没有明显区别(sha256的算法标记位是$5$),具体差异参考:https://passlib.readthedocs.io/en/stable/lib/passlib.hash.sha512_crypt.html#passlib.hash.sha512_crypt

passlib代码写的比较绕(至少我看起来是这样。。。可能是我比较弱。。。),总是找不到具体执行加密的地方,但是大致看明白了,有两种方式,一种是使用操作系统hash加密算法库,另一种是如果操作系统没提供相关算法,就使用passlib内置算法(就是自己写的一套算法实现),相比较而言操作系统提供的算法库更快更高效,一般Linux都提供了常见的加密算法,sha512就是其中之一。

keystone创建用户流程

修改密码流程

修改密码场景首先需要用旧密码获取token之后进行修改,或者管理员权限进行修改。整体流程与创建用户非常接近,参考上面的创建用户流程即可分析清楚。更新密码有专门的API接口和对应的method,也可以通过update user API来实现,keystone代码流程最终都是走的update_user。

用户认证

获取token

认证就是用户登录或者说获取token的过程(登录就是一种比较特殊的获取token的过程,可参考之前写的一篇文章:OpenStack Dashboard用户登录为啥不需要输入租户名),因此这里只讨论根据用户名密码租户等信息生成token的过程,根据token操作的API(url),以及keystone-paste.ini配置的composite、app、filter,找到对应的router和controller:

验证token

api入口跟获取类似,

@controller.protected()这个装饰器里面做的事情比较多,检查了x-auth-token(权限比较高的认证token)和x-subject-token(被验证的token),

validate_non_persistent_token这个方法上面已经介绍过。

可以看出,一次token认证过程,keystone/token/providers/common.py(507)get_token_data()这个方法走了3次,一次x-auth-token(protected装饰器里),两次x-subject-token(protected装饰器里和主流程里)。

这里可以看出两次x-subject-token流程貌似有点多余,但我还没看明白为啥要两次。

以nova-api服务为例进行第三方服务token验证流程分析。

可以看到通过keystonemiddleware来进行token认证,keystonemiddleware中会根据token格式判断是否可以本地验证,如PKI格式等,否则就需要发送请求给keystone服务进行token的验证,成功后,把response传递给下一个filter(keystonecontext),keystonecontext filter会把token信息转换成nova的context对象,然后这个context就可以在请求经过的所有nova子服务中进行流转使用。keystonecontext实现是nova.api.auth.NovaKeystoneContext#__call__,返回的对象是context.RequestContext。

keystonemiddleware的用法参考:

关于@webob.dec.wsgify(RequestClass=wsgi.Request),这部分我也没看懂,大致就是把方法转换成WSGI app,比较省事儿。参考:

用户授权

角色管理

keystone角色的增删改查流程就不分析了,跟上面的用户很类似。

根据角色进行授权

keystone默认有两种角色,admin和_member_,分别表示管理员和普通用户,也可以增加自己的角色,但是需要修改授权策略配置文件,授权配置文件一般在服务配置目录下,比如/etc/nova/policy.json、/etc/neutron/policy.json(json格式的授权策略配置已经要被废弃了,后续会切换到yaml格式),以nova的配置为例:

以nova创建虚拟机接口为例:

nova.policy.enforce(context, act, target)最终是使用oslo_policy.policy的enforce方法来进行授权检查的,该方法会首先加载授权配置文件中的全部规则,并且缓存起来,只有当传入了force_reload参数或者策略配置文件修改时间有变动时,才重新加载,所以修改策略配置文件后,下次发送请求给API服务即可立即生效。

oslo_policy这个库代码不多,单步调试也不复杂,主要流程在于策略的加载检查,我也没有去实际的调试,后面用到再说吧。

其他WSGI框架使用的认证和授权方案

上面写了那么多,实际我只有一个想法,解决困扰了我很久的问题:web服务端如何实现认证和授权?是不是每个后端接口都要手工加上认证、授权相关方法?用户怎么登录?登录的用户有什么权限、能执行哪些操作?

所以看完OpenStack相关服务的认证和授权方式之后,又查看了一些python web框架的认证、授权方式,比如最简单的bottle.py,它本身并没有实现相关功能,而是集成第三方库Bottle-Cork(当然不止这一个库,还有其他的),示例代码:http://cork.firelet.net/example_webapp_decorated.htmlhttp://cork.firelet.net/example_webapp.html,一种是用装饰器实现认证、授权,第二种是显式的加到方法里去。

而OpenStack则是基于paste deploy库的authtoken filter来实现的认证,授权是显式的写在需要进行授权检查的操作方法里。

Django框架是MVC集成模式,所以这方面做的很完善:http://python.usyiyi.cn/translate/django_182/topics/auth/default.html,有很多现成的方法可以直接用起来。web请求可以配合session来记录用户登录信息,就不必要每次发请求都要把用户名密码发给后端,或者把token带到header里面。

OpenStack各个服务只是实现HTTP RestFul API(包含model和controller),并没有web视图(views),所以也不涉及session,只能通过token来进行请求的认证和授权(token有过期时间限制,不用每次发送用户名密码,更安全)。

使用Flask框架设计RestFul API认证:http://www.pythondoc.com/flask-restful/third.html

可以看出也是基于token的认证,并且文章还解释了使用用户名密码方式认证的问题,与上面我提到的一样,只是多了一条,http客户端必须要明文保存用户名密码,否则无法发送到服务端,这样也是不安全的。

另外上面的文章还提到,基于token的认证跟flask处理基于session cookies的认证方法是类似的,都是基于一个叫itsdangerous的库,因此可以实现cookies中缓存的认证信息在多久之后就失效,防止永不失效或者失效时间过长带来的安全风险。

还有其他很多的认证方法比如OAuth及OAuth2.0,还有OpenID、LADP等,这部分没研究。但目标应该都是大同小异,减少客户端每次发送用户名密码带来的安全风险,并保证token的安全不易破解。

参考: