keystone整体认识--学习与思考

此次学习过程,主要需要搞清四个问题:

(1)Keystone框架:服务端、客户端

(2)用户管理机制:如何验证用户身份、维护用户身份

(3)多租户的机制:从之前的单一模式,变化至今的原因,如何实现用户、角色、和租户的关联

(4)Token的相关机制:Token的使用好处、如何保存和维护(之前一直想有时候调试,过一段时间提示重新获取token,觉得好麻烦,可是如果不这样,安全性又降低了。。。),有了Token,如何进行身份认证与鉴权。


(1)首先第一个问题,通过多次地调试接口,发现dashborad很多的功能,都是借助于客户端的接口封装。而客户端与服务端的关系又是怎样的呢?

服务端是通过keystone-all 这个脚本来启动服务的,相关的核心代码如下:

servers.append(create_server(paste_config,
                             'admin',
                             CONF.eventlet_server.admin_bind_host,
                             CONF.eventlet_server.admin_port,
                             admin_worker_count))
servers.append(create_server(paste_config,
                             'main',
                             CONF.eventlet_server.public_bind_host,
                             CONF.eventlet_server.public_port,
                             public_worker_count))
    开了两类服务,一类是端口35357的管理服务,另一个是端口是5000的公共服务,而Openstack主要通过Restful的方式实现HTTP服务,并且通过PasteDeploy来实现定制。

keystone的paste.ini配置文件的定义入口主要有2个,分别是composite:main(公共的)和composite:admin(管理员的),【为啥分这个两个呢?是由于有些服务,只能管理员权限才能操作】而每个入口对应了3个url映射,/v2.0、/v3和/。这与版本的更新 与兼容前面的版本有很大的关联。

    对于pasete的配置文件的认识,在此不详细介绍,只需明白,每一个pipeline ,按序执行,并且前面的都是中间件,最后一个才是服务。主要的过滤器,包括user_crud_extension(添加修改密码的url,公共服务)\crud_extension(添加对用户、租户、角色、服务和端点等资源管理的url映射),也可以看出权限的区别。

  • user_crud_extension
      工厂类是 CrudExtension,主要的方法在add_routes中,
def add_routes(self, mapper):
    user_controller = UserController()

    mapper.connect('/OS-KSCRUD/users/{user_id}',
                   controller=user_controller,
                   action='set_user_password',
                   conditions=dict(method=['PATCH']))
该方法添加了一条/OS-KSCRUD/users/{user_id}的URL映射,服务器收到这条URL后会调用UserController类中的set_user_password的方法。mapper是将URL 映射到controller中的对应处理方法。
  • crud_extension

工厂类是CrudExtension,主要的方法在add_routes中,

def add_routes(self, mapper):
    tenant_controller = resource.controllers.Tenant()#租户
    assignment_tenant_controller = (                         
        assignment.controllers.TenantAssignment())   #租户分配
    user_controller = identity.controllers.User()    #用户
    role_controller = assignment.controllers.Role()  #角色
    assignment_role_controller = assignment.controllers.RoleAssignmentV2() #角色分配
    service_controller = catalog.controllers.Service()#服务
    endpoint_controller = catalog.controllers.Endpoint()#端点
admin的方法比public的URL丰富很多,主要是完成URL与controller的映射。

从上述的controller中,大致我们可以看出服务包括:

The Identity service,提供身份认证、用户数据(CRUD)认证;
The Resource service,提供项目和域的数据;
The Assignment service,提供权限、及其分配相关的任务;
The Token service,认证并管理令牌;
The Catalog service,目录服务提供了一种用于端点的端点注册中心发现;
The Policy service,政策服务提供了一个基于规则的授权引擎和相关规则管理界面。

下面以两个典型服务作为介绍,public_service 和 admin_service。

  • public_service
@fail_gracefully
def public_app_factory(global_conf, **local_conf):
    controllers.register_version('v2.0')
    return wsgi.ComposingRouter(routes.Mapper(),
                                [assignment.routers.Public(),
                                 token.routers.Router(),
                                 routers.VersionV2('public'),
                                 routers.Extension(False)])
最重要的是最后一句return,返回了4个路由对象,
对象名称描述
assignment.routers.Public
根据用户的Token获取其所属的租户
token.routers.Router
实现了token相关的路由,包括用户身份认证、Token认证、查询、撤销和删除等
routers.VersionV2
用于服务版本信息查询url路由
routers.Extension
用于扩展API的信息查询路由

ComposingRouter对象初始化时,会依次调用这4个路由对象的add_routes方法,添加相应的URI。以其中一个作为分析, assignment.routers.Public的代码:
class Public(wsgi.ComposableRouter):
    def add_routes(self, mapper):
        tenant_controller = controllers.TenantAssignment()
        mapper.connect('/tenants',
                       controller=tenant_controller,
                       action='get_projects_for_token',
                       conditions=dict(method=['GET']))
可见这条URL是根据用户的token可以查出相应的项目信息,对于的方法是GET,类似的方法还有POST、PUT、DELETE。
  • admin_service

@fail_gracefully
def admin_app_factory(global_conf, **local_conf):
    controllers.register_version('v2.0')
    return wsgi.ComposingRouter(routes.Mapper(),
                                [identity.routers.Admin(),
                                 assignment.routers.Admin(),
                                    token.routers.Router(),
                                    resource.routers.Admin(),
                                    routers.VersionV2('admin'),
                                    routers.Extension()])

最大的区别,就是admin_service对象添加了assignment.routers.Admin对象,它是基于crud_extension的基础上,定义了额外的RUL映射。

上面简单地介绍了服务端的架构,下面来探究客户端的架构。

keystone客户端目前有V2和V3两个版本,通过调试接口发现,多用V2.0的版本,因此,这里以2.0作为介绍。

def __init__(self, **kwargs):
    """Initialize a new client for the Keystone v2.0 API."""

    if not kwargs.get('session'):
        warnings.warn(
            'Constructing an instance of the '
            'keystoneclient.v2_0.client.Client class without a session is '
            'deprecated as of the 1.7.0 release and may be removed in '
            'the 2.0.0 release.', DeprecationWarning)

    super(Client, self).__init__(**kwargs)

    self.certificates = certificates.CertificatesManager(self._adapter)#认证
    self.endpoints = endpoints.EndpointManager(self._adapter)          #端口
    self.extensions = extensions.ExtensionManager(self._adapter)       #扩展
    self.roles = roles.RoleManager(self._adapter)                      #角色
    self.services = services.ServiceManager(self._adapter)             #服务
    self.tokens = tokens.TokenManager(self._adapter)                   #Token
    self.users = users.UserManager(self._adapter, self.roles)          #用户
    self.tenants = tenants.TenantManager(self._adapter,              
                                         self.roles, self.users)       #项目
    # extensions
    self.ec2 = ec2.CredentialsManager(self._adapter)                   #用于扩展
    if not kwargs.get('session') and self.management_url is None:
        self.authenticate()                                            #用户认证
    client的初始化定义在上面代码中,可见不同资源有各自的Manager对象,并且在初始化的时候,就对用户身份进行验证。client类继承自HTTPClient类,其中以 TenantManager为例进行介绍,这个类继承自ManagerWithBind,而ManagerWithBind 继承自Manager类。在Manager类中定义了基本的_get,_delete,_post,_put等方法,这些方法最终会调用HTTPClient中的方法发送HTTP请求。(eng.   tenant.get方法先调用的是Manager中的get方法,查看其方法,发现Manager的初始化,有一条self.api=api,此处即将client的对象传递进来,所以,最终调用的就是HTTPClient方法),至于client在初始化时是如何对用户身份进行验证的,我在第2个问题中介绍。

(2)下面来看一下用户管理的相关策略、技术。

在使用dashboard的过程中,我们会发现,用户的很多操作,都依赖于全局管理员的分配,比如创建用户、关联租户,这是没有任何异议的。可是全局管理员只配了一个,并且是单层的,尽管说可以赋予别的用户管理员权限,那么这样,这个用户的权限也是全局管理员了,那么这个危险性可想而知,而如果所有的安排都交给全局管理员,又太累人了,所以出现了角色的概念,对于角色,我想它应该是可以配置的,但是有没有角色嵌套的关联?还得进一步确认。

我们先来看看用户的认证是如何进行的。前提:用户认证有用户名密码认证方式和admin_token方式两种,认证涉及客户端和服务端,首先是客户端,经过第一个问题的理解,得知客户端认证的代码实质在HTTPClient类中,核心部分如下,

auth_url = auth_url or self.auth_url
user_id = user_id or self.user_id
username = username or self.username
password = password or self.password

user_domain_id = user_domain_id or self.user_domain_id
user_domain_name = user_domain_name or self.user_domain_name
domain_id = domain_id or self.domain_id
domain_name = domain_name or self.domain_name
project_id = project_id or tenant_id or self.project_id
project_name = project_name or tenant_name or self.project_name
project_domain_id = project_domain_id or self.project_domain_id
project_domain_name = project_domain_name or self.project_domain_name

trust_id = trust_id or self.trust_id
region_name = region_name or self._adapter.region_name

if not token:
    token = self.auth_token_from_user
    if (not token and self.auth_ref and not
            self.auth_ref.will_expire_soon(self.stale_duration)):
        token = self.auth_ref.auth_token

kwargs = {
    'auth_url': auth_url,
    'user_id': user_id,
    'username': username,
    'user_domain_id': user_domain_id,
    'user_domain_name': user_domain_name,
    'domain_id': domain_id,
    'domain_name': domain_name,
    'project_id': project_id,
    'project_name': project_name,
    'project_domain_id': project_domain_id,
    'project_domain_name': project_domain_name,
    'token': token,
    'trust_id': trust_id,
}
(keyring_key, auth_ref) = self.get_auth_ref_from_keyring(**kwargs)
new_token_needed = False
if auth_ref is None or self.force_new_token:
    new_token_needed = True
    kwargs['password'] = password
    resp = self.get_raw_token_from_identity_service(**kwargs)

    if isinstance(resp, access.AccessInfo):
        self.auth_ref = resp
    else:
        self.auth_ref = access.AccessInfo.factory(*resp)

    if region_name:
        self.auth_ref.service_catalog._region_name = region_name
else:
    self.auth_ref = auth_ref

self.process_token(region_name=region_name)
if new_token_needed:
    self.store_auth_ref_into_keyring(keyring_key)
return True

获取外部传入的参数,紧接着(keyring_key, auth_ref) = self.get_auth_ref_from_keyring(**kwargs)从本地缓存尝试获取token,(keyring是一个专门负责用户敏感数据的应用程序),如果本地token不存在或者即将过期,就向服务端发送请求,获取新的token。

self.process_token(region_name=region_name)设置用户相关的认证信息,self.store_auth_ref_into_keyring(keyring_key),将用户的token保存在本地。可见最后返回的是True,那么如果认证不通过在哪里体现呢?看这一句resp = self.get_raw_token_from_identity_service(**kwargs),如果验证不通过,就在此抛出异常。
 

  • 服务端认证
服务端的admin_token认证方式比较简单,在过滤器admin_token_auth中,发现处理方法AdminTokenAuthMiddleware(),对传输进来的Token进行简单的对比,重点是另一个

用户名密码登入方式的认证。

以public_service为例,其处理的方法是public_app_factory,这个方法中有4条路由,其中 token.routers.Router()这条路由实现了用户名、密码认证,它的主要代码如下:

class Router(wsgi.ComposableRouter):

    def add_routes(self, mapper):

        token_controller = controllers.Auth()

        mapper.connect('/tokens',

                       controller=token_controller,

                       action='authenticate',

                       conditions=dict(method=['POST']))

这个方法调用的token_controller 的authenticate方法,    

def authenticate(self, context, auth=None):

        if auth is None:----------------------------------------------------------------根据传入进来的auth相关信息,如果判断为空,则直接抛出异常

            raise exception.ValidationError(attribute='auth',

                                            target='request body')

        if "token" in auth:-------------------------------------------------------------根据auth进行认证

            auth_info = self._authenticate_token(

                context, auth)

        else:

            # Try external authentication

            try:

                auth_info = self._authenticate_external( ------------------------------------------尝试外表认证

                    context, auth)

            except ExternalAuthNotApplicable:

                # Try local authentication

                auth_info = self._authenticate_local(--------------------------------------------------本地认证

                    context, auth)

*********************

进行本地认证的代码解析:

token/controllers.py(259)_authenticate_local()if 'passwordCredentials' not in auth,检测passwordCredentials是否存在,否则出错;检测password在passwordCredentials中是否存在;检测长度是否超出限制;检测passwordCredentials是否存在userId或者username;如果是username存在,则根据username获取userId,self.identity_api.get_user_by_name(username,          CONF.identity.default_domain_id)根据user_id\password进行用户身份认证:user_ref = self.identity_api.authenticate(context,user_id=user_id,password=password)   keystone/identity/core.py(723)authenticate():domain_id, driver, entity_id = (self._get_domain_driver_and_entity_id(user_id))获取域id、后端驱动程序、实体idref = driver.authenticate(entity_id, password)----进行身份验证,即对比密码,真正的比较是keystone\common\utils获取tenant_id,根据auth获取,tenant_id = self._get_project_id_from_auth(auth)根据用户id和tenantid获取项目信息及项目角色tenant_ref, metadata_ref['roles'] = self._get_project_roles_and_ref(user_id, tenant_id)项目根据用户id、获取角色列表时,首先,self.resource_api.get_project(tenant_id)然后获取user_role_list,再获取  group_role_list,过期时间,        expiry = provider.default_expire_time()        bind = None        audit_id = None        return (user_ref, tenant_ref, metadata_ref, expiry, bind, audit_id)

*******************

        user_ref, tenant_ref, metadata_ref, expiry, bind, audit_id = auth_info

  

        try:-----------------------------------------------------------------------------------------------------------------确保用户及项目是可用的

            self.identity_api.assert_user_enabled(

                user_id=user_ref['id'], user=user_ref)

            if tenant_ref:

                self.resource_api.assert_project_enabled(

                    project_id=tenant_ref['id'], project=tenant_ref)

        except AssertionError as e:

            six.reraise(exception.Unauthorized, exception.Unauthorized(e),

                        sys.exc_info()[2])

        user_ref = self.v3_to_v2_user(user_ref)-----------------------------------------------------------去掉V2版本不支持的元数据

        if tenant_ref:

            tenant_ref = self.v3_to_v2_project(tenant_ref)

        auth_token_data = self._get_auth_token_data(user_ref,

                                                    tenant_ref,

                                                    metadata_ref,

                                                    expiry,

                                                    audit_id)---------------------------------------------------------------------格式化token

        if tenant_ref:

            catalog_ref = self.catalog_api.get_catalog(

                user_ref['id'], tenant_ref['id'])---------------------------------------------------------------------获取服务端点列表

********************

服务端点列表解析:

def get_catalog(self, user_id, tenant_id):触发底层driver---sql的get_catalog方法,keystone/catalog/backends/sql.py(304)get_catalog()catalog_ref= {}formatted_url = core.format_url(endpoint['url'], substitutions,silent_keyerror_failures=silent_keyerror_failures)url:(当前) http://10.133.6.67:8776/v2/$(tenant_id)s'http://10.133.6.67:8776/v2/ac47e1007d3342f2820576ff78c9e4c0' region = endpoint['region_id']service_type = endpoint.service['type']获取端点service对应的type;---volumev2default_service:       'id': endpoint['id'],'name': endpoint.service.extra.get('name', ''),'publicURL': ''   catalog的格式:catalog.setdefault(region, {})                 catalog[region].setdefault(service_type, default_service)interface_url = '%sURL' % endpoint['interface']catalog[region][service_type][interface_url] = url

********************

        else:

            catalog_ref = {}

        auth_token_data['id'] = 'placeholder'

        if bind:

            auth_token_data['bind'] = bind

        roles_ref = []

        for role_id in metadata_ref.get('roles', []):--------------------------------------------------------获取用户角色

            role_ref = self.role_api.get_role(role_id)

            roles_ref.append(dict(name=role_ref['name']))

        (token_id, token_data) = self.token_provider_api.issue_v2_token(

            auth_token_data, roles_ref=roles_ref, catalog_ref=catalog_ref)

        if CONF.trust.enabled and 'trust_id' in auth:

            self.trust_api.consume_use(auth['trust_id'])

        return token_data------------------------------------------------------------------------------------返回token,主要包含ID、角色、服务目录

(3)多租户机制

Openstack中主要通过角色、租户、用户这三类资源来进行用户权限的管理。首先,

租户管理:

租户管理的URL主要涉及到curd_extension过滤器和public_service\admin_service,

上图展示的是tenant控制器的相关方法,其它类似。
角色管理:从前面我们知道Token中包含role,因此如果删除了角色,用户的token也需要删除,以role_controller的remove_from_user为例,

    def remove_role_from_user(self, context, user_id, role_id, tenant_id=None):
        self.assert_admin(context)------------------------------------------------------------需要管理员权限
        if tenant_id is None:----------------------------------------------------------------------角色必须与租户关联,因此不能为空
            raise exception.NotImplemented(
                message=_('User roles not supported: tenant_id required'))
        self.assignment_api.remove_role_from_user_and_project( user_id, tenant_id, role_id)-----------再深入分析该方法,
主要包含三个方法,
        1)self.driver.remove_role_from_user_and_project(user_id, project_id,
                                                      role_id)
        2)self.identity_api.emit_invalidate_user_token_persistence(user_id)
        3)self.revoke_api.revoke_by_grant(role_id, user_id=user_id,
                                        project_id=project_id)

其中1)的方法是删除用户在租户中的角色,
      2)收回用户token
      3)收回事件
权限管理:
在前面的分析中,我们看到assert_admin的方法,是检查当前的用户是不是管理员,即权限的认证,以此方法来进行权限的剖析。     
if not context['is_admin']:
    user_token_ref = utils.get_token_ref(context)------------获取token
    
#*****<KeystoneToken (audit_id=ZmM_TymLQ2iEU24qZjTkhQ, audit_chain_id=ZmM_TymLQ2iEU2#4qZjTkhQ) at 0x48dd3b0>****
    validate_token_bind(context, user_token_ref)------验证token的绑定情况
    creds = copy.deepcopy(user_token_ref.metadata)
    
    try:
        creds['user_id'] = user_token_ref.user_id---------获取用户ID
    except exception.UnexpectedError:
        LOG.debug('Invalid user')
        raise exception.Unauthorized()
    
    if user_token_ref.project_scoped:
        creds['tenant_id'] = user_token_ref.project_id----获取项目ID
    else:
        LOG.debug('Invalid tenant')
        raise exception.Unauthorized()

    creds['roles'] = user_token_ref.role_names-----------获取角色
    # Accept either is_admin or the admin role
    self.policy_api.enforce(creds, 'admin_required', {})
如果是admin_token的认证方式,则直接通过;否则,一步步地执行上述方法,最后一步检验管理员权限。该方法会调用policy_api的enforce方法,而sql = keystone.policy.backends.sql:Policy,即是它的实例化,enforce方法定义在rules.Policy类中,具体如下:
def enforce(credentials, action, target, do_raise=True):
 
 
#credentials:{'user_id': u'ab99a024d10d46b6b35cb95d69955d84', u'is_admin': 0, u'roles': [u'admin'], 'tenant_id': u'321fd6e24a5643249acbf4eecc659769'}
#action:admin_required   即rule
#
#
    init()---------------------加载policy文件
#init 分析:
#_ENFORCER = common_policy.Enforcer(CONF) 
    extra = {}
    if do_raise:
        extra.update(exc=exception.ForbiddenAction, action=action,
                     do_raise=do_raise)

    return _ENFORCER.enforce(action, target, credentials, **extra)

##
最后一句 _ENFORCER.enforce(action, target, credentials, **extra)的解析:
      在python2.7/site-packages/oslo_policy/policy.py(463)enforce():
                 self.load_rules()-----------------
(1)self.policy_path = self._get_policy_path(self.policy_file) 是查找policy文件的路径,查找的默认路径有,cfg_dirs = [  _fixpath(os.path.join('~', '.' + project)) if project else None,  _fixpath('~'),   os.path.join('/etc', project) if project else None,  '/etc'   ](2)self._load_policy_file(self.policy_path, force_reload,overwrite=self.overwrite)是加载policy文件,检测是否重新加载policy文件的考量主要是if not cache_info or mtime > cache_info.get('mtime', 0),即没有缓存或者加载的policy文件时间过旧(3)查找conf路径下的policy.d文件,不存在抛出异常,并继续。
                 if isinstance(rule, _checks.BaseCheck):
                            result = rule(target, creds, self)
                 elif not self.rules:
                             result = False
                 else:
                             try:
                                     result = self.rules[rule](target, creds, self)-------关键是这一句的执行,creds是{'user_id': u'ab99a024d10d46b6b35cb95d69955d84', u'is_admin': 0, u'roles': [u'admin'], 'tenant_id': u'321fd6e24a5643249acbf4eecc659769'},从creds中提取role的信息,并于policy中的对比。
                             ... 
  
##

Keystone 在每个子模块的 controller.py 文件中的各个控制类的方法上实施 RBAC。protected 和 filterprotected 在 /keystone/common/controller.py 中实现。protected/filterprotected 最终会调用 /keystone/openstack/common/policy.py 中的 enforce 方法。还有一种就是上面提到的self.policy_api.enforce(creds, 'admin_required', {})。
(4)Token的相关机制
以生产Token的流程为例进行解析,
POST /v2.0/Tokens 
if auth is None:
    raise exception.ValidationError(attribute='auth',
                                   target='request body')
#首先,判断auth的请求体是否为空,为空则抛出异常,auth={u'tenantName': u'admin', u'passwordCredentials': {u'username': u'admin', u'password': u'123456'}} 

if "token" in auth: # Try to authenticate using a token auth_info = self._authenticate_token( context, auth)#如果用token进行身份认证,则执行该方法

else:
    # Try external authentication
    try:
        auth_info = self._authenticate_external(
            context, auth)
#进行外部认证,外表认证中获取environment.get('REMOTE_USER'),如果不存在则抛出异常。

    except ExternalAuthNotApplicable:
        # Try local authentication
        auth_info = self._authenticate_local(
            context, auth)
#在异常中进行本地认证:if 'passwordCredentials' not in auth,检测passwordCredentials是否存在,否则出错;检测password在passwordCredentials中是否存在;检测长度是否超出限制;检测passwordCredentials是否存在userId或者username;如果是username存在,则根据username获取userId,self.identity_api.get_user_by_name(username, CONF.identity.default_domain_id);根据user_id\password进行用户身份认证:user_ref = self.identity_api.authenticate(context,user_id=user_id,password=password),
keystone/identity/core.py(723)authenticate():
	domain_id, driver, entity_id = (self._get_domain_driver_and_entity_id(user_id))
	获取域id、后端驱动程序、实体id
	ref = driver.authenticate(entity_id, password)----进行身份验证,即对比密码,真正的比较是keystone\common\utils
	获取tenant_id,根据auth获取,
	tenant_id = self._get_project_id_from_auth(auth)
	根据用户id和tenantid获取项目信息及项目角色
	tenant_ref, metadata_ref['roles'] = self._get_project_roles_and_ref(user_id, tenant_id)项目
		根据用户id、获取角色列表时,首先,self.resource_api.get_project(tenant_id)
						然后获取user_role_list,
						再获取  group_role_list,
	
	过期时间,
        expiry = provider.default_expire_time()
        bind = None
        audit_id = None
        return (user_ref, tenant_ref, metadata_ref, expiry, bind, audit_id)

user_ref, tenant_ref, metadata_ref, expiry, bind, audit_id = auth_info
# Validate that the auth info is valid and nothing is disabled
try:
    self.identity_api.assert_user_enabled(
        user_id=user_ref['id'], user=user_ref)
    if tenant_ref:
        self.resource_api.assert_project_enabled(
            project_id=tenant_ref['id'], project=tenant_ref)
#确保用户、项目是激活的。enable=true
except AssertionError as e:
    six.reraise(exception.Unauthorized, exception.Unauthorized(e),
                sys.exc_info()[2])
# NOTE(morganfainberg): Make sure the data is in correct form since it
# might be consumed external to Keystone and this is a v2.0 controller.
# The user_ref is encoded into the auth_token_data which is returned as
# part of the token data. The token provider doesn't care about the
# format.
user_ref = self.v3_to_v2_user(user_ref)
if tenant_ref:
    tenant_ref = self.v3_to_v2_project(tenant_ref)
#去掉V2版本不支持的元数据
auth_token_data = self._get_auth_token_data(user_ref,
                                            tenant_ref,
                                            metadata_ref,
                                            expiry,
                                            audit_id)
# auth_tken_data = {'expires': datetime.datetime(2015, 10, 9, 7, 54, 46, 271719), 'parent_audit_id': None, 'user': {'username': u'admin', 'enabled': True, 'name': u'admin', 'id': u'ab99a024d10d46b6b35cb95d69955d84'}, 'tenant': {'description': u'', 'enabled': True, 'id': u'321fd6e24a5643249acbf4eecc659769', 'name': u'admin'}, 'metadata': {'roles': [u'0d7538a5e2fd47278c0624500450f276']}}

if tenant_ref:
    catalog_ref = self.catalog_api.get_catalog(
        user_ref['id'], tenant_ref['id'])
#触发底层driver---sql的get_catalog方法,keystone/catalog/backends/sql.py(304)get_catalog(),substitutions = dict(itertools.chain(CONF.items(), CONF.eventlet_server.items())),substitutions.update({'user_id': user_id}),substitutions.update({'tenant_id': tenant_id}), 
formatted_url = core.format_url(endpoint['url'], substitutions,silent_keyerror_failures=silent_keyerror_failures),
	url:(当前) http://10.133.6.67:8776/v2/$(tenant_id)s
	'http://10.133.6.67:8776/v2/ac47e1007d3342f2820576ff78c9e4c0'
region = endpoint['region_id']
service_type = endpoint.service['type']获取端点service对应的type;---volumev2
default_service:
			        'id': endpoint['id'],
				'name': endpoint.service.extra.get('name', ''),
				'publicURL': ''
catalog的格式:
		 catalog.setdefault(region, {})
                 catalog[region].setdefault(service_type, default_service)
		 interface_url = '%sURL' % endpoint['interface']
		 catalog[region][service_type][interface_url] = url
Endpoint的数据格式如下:
+----------------------------------+----------------------------------+-----------+----------------------------------+----------------------------------------------+-------+---------+-----------+
| id                               | legacy_endpoint_id               | interface | service_id                       | url                                          | extra | enabled | region_id |
+----------------------------------+----------------------------------+-----------+----------------------------------+----------------------------------------------+-------+---------+-----------+
| 253d2388f18c4457b6b6707a58cecde0 | NULL                             | admin     | 5cc18913a64c4433b4503c8b8d545b7e | http://10.133.16.229:8774/v2.1/$(tenant_id)s | {}    |       1 | RegionOne |
| 313d87dea79c43bf984e245337c74a05 | NULL                             | public    | efdfbb4f79044f1a93d240b503b4e107 | http://10.133.16.229:9696/                   | {}    |       1 | RegionOne 

else:
    catalog_ref = {}

auth_token_data['id'] = 'placeholder'
if bind:
    auth_token_data['bind'] = bind

roles_ref = []
for role_id in metadata_ref.get('roles', []):
    role_ref = self.role_api.get_role(role_id)
    roles_ref.append(dict(name=role_ref['name']))
#根据role-id置换成角色名称
(token_id, token_data) = self.token_provider_api.issue_v2_token(
    auth_token_data, roles_ref=roles_ref, catalog_ref=catalog_ref)
#CONF.token.provider=uuid,目前有四种方式生成token:fernet、uuid、pki、pkiz,此例配置用uuid生成,

def issue_v2_token(self, token_ref, roles_ref=None,
                   catalog_ref=None):
    metadata_ref = token_ref['metadata']
    trust_ref = None
    if CONF.trust.enabled and metadata_ref and 'trust_id' in metadata_ref:
        trust_ref = self.trust_api.get_trust(metadata_ref['trust_id'])

    token_data = self.v2_token_data_helper.format_token(
        token_ref, roles_ref, catalog_ref, trust_ref)
    token_id = self._get_token_id(token_data)
    token_data['access']['token']['id'] = token_id
    return token_id, token_data
#最终生成token的过程在self.v2_token_data_helper.format_token()中,expires = utils.isotime(expires)过期时间格式转换,token的格式如下:
o = {'access': {'token': {'id': token_ref['id'],
                          'expires': expires,
                          'issued_at': utils.strtime(),
                          'audit_ids': audit_info
                          },
                'user': {'id': user_ref['id'],
                         'name': user_ref['name'],
                         'username': user_ref['name'],
                         'roles': roles_ref,
                         'roles_links': metadata_ref.get('roles_links',
                                                         [])
                         }
                }
     }
这是初步的格式,后续进一步补充,o['access']['token']['tenant'] = token_ref['tenant'],
								o['access']['serviceCatalog'] = V2TokenDataHelper.format_catalog(catalog_ref)
				o['access']['metadata'] = {'is_admin': 0},
				o['access']['metadata']['roles'] = metadata_ref['roles']
				如果支持CONF.trust.enabled,还需添加:
				
				o['access']['trust'] = {'trustee_user_id':
                        		trust_ref['trustee_user_id'],
                       	 		'id': trust_ref['id'],
                        		'trustor_user_id':
                        		trust_ref['trustor_user_id'],
                        		'impersonation':
                        			trust_ref['impersonation']
                        				}
生成了token后,继续完善token的格式,最终返回token_id和token_data。
如果需要持久化,self._needs_persistence,则存储在session中。
				self._create_token(token_id, data),
					token_ref = TokenModel.from_dict(data_copy)
					token_ref.valid = True
					session = sql.get_session()
					with session.begin():
    						session.add(token_ref)				
# NOTE(wanghong): We consume a trust use only when we are using trusts
# and have successfully issued a token.
if CONF.trust.enabled and 'trust_id' in auth:
    self.trust_api.consume_use(auth['trust_id'])

return token_data

































  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值