stackstorm 26. 源碼分析之----stackstorm的auth服務分析

代碼在/home/machao/backup/new_comment/st2_dir/2.6_dir/commpany_2.6/st2/st2auth/st2auth

目標:
弄清楚st2auth的原理

1 總入口
發送auth校驗請求的url樣例如下:
http://st2auth:9100/tokens
被st2auth服務接收到該請求後,進入如下代碼:
st2/st2auth/st2auth/controllers/v1/root.py
代碼如下:
from st2auth.controllers.v1 import auth


class RootController(object):
    tokens = auth.TokenController()

然後進入:
st2/st2auth/st2auth/controllers/v1/auth.py
代碼:
class TokenController(object):
    validate = TokenValidationController()

    def __init__(self):
        try:
            self.handler = HANDLER_MAPPINGS[cfg.CONF.auth.mode]()
        except KeyError:
            raise ParamException("%s is not a valid auth mode" %
                                 cfg.CONF.auth.mode)

    def post(self, request, **kwargs):
        headers = {}
        if 'x-forwarded-for' in kwargs:
            headers['x-forwarded-for'] = kwargs.pop('x-forwarded-for')

        authorization = kwargs.pop('authorization', None)
        if authorization:
            authorization = tuple(authorization.split(' '))

        token = self.handler.handle_auth(request=request, headers=headers,
                                         remote_addr=kwargs.pop('remote_addr', None),
                                         remote_user=kwargs.pop('remote_user', None),
                                         authorization=authorization,
                                         **kwargs)
        return process_successful_response(token=token)


分析:
1.1) __init__方法
HANDLER_MAPPINGS = {
    'proxy': handlers.ProxyAuthHandler,
    'standalone': handlers.StandaloneAuthHandler
}
查看/etc/st2/st2.conf內容
[auth]
# Base URL to the API endpoint excluding the version (e.g. http://myhost.net:9101/)
api_url = http://st2api:9101
mode = standalone
# Note: Settings below are only used in "standalone" mode
# backend: flat_file
# backend_kwargs: '{"file_path": "/etc/st2/htpasswd"}'
backend = keystone
backend_kwargs = {"keystone_url": "http://keystone-api.openstack.svc.cluster.local:80", "keystone_version": 3, "keystone_mode": "email"}

所以:
cfg.CONF.auth.mode對應值是standalone

1.2) 分析
        token = self.handler.handle_auth(request=request, headers=headers,
                                         remote_addr=kwargs.pop('remote_addr', None),
                                         remote_user=kwargs.pop('remote_user', None),
                                         authorization=authorization,
                                         **kwargs)

進入:
handlers.StandaloneAuthHandler的handle_auth方法
具體進入文件:
st2/st2auth/st2auth/handlers.py
代碼如下:
class StandaloneAuthHandler(AuthHandlerBase):
    def __init__(self, *args, **kwargs):
        self._auth_backend = get_backend_instance(name=cfg.CONF.auth.backend)
        super(StandaloneAuthHandler, self).__init__(*args, **kwargs)

    def getTokenId(self, request):
        token = getattr(request, 'tokenId', None)
        return token

    def handle_auth(self, request, headers=None, remote_addr=None, remote_user=None,
                    authorization=None, **kwargs):
        auth_backend = self._auth_backend.__class__.__name__

        extra = {'auth_backend': auth_backend, 'remote_addr': remote_addr}

        if not authorization:
            LOG.audit('Authorization header not provided', extra=extra)
            abort_request()
            return

        auth_type, auth_value = authorization
        if auth_type.lower() not in ['basic']:
            extra['auth_type'] = auth_type
            LOG.audit('Unsupported authorization type: %s' % (auth_type), extra=extra)
            abort_request()
            return

        try:
            auth_value = base64.b64decode(auth_value)
        except Exception:
            LOG.audit('Invalid authorization header', extra=extra)
            abort_request()
            return

        split = auth_value.split(':', 1)
        if len(split) != 2:
            LOG.audit('Invalid authorization header', extra=extra)
            abort_request()
            return

        username, password = split

        request_token = self.getTokenId(request)
        result = self._auth_backend.authenticate(username=username,
                                                 password=password,
                                                 tokenId=request_token)

        if result is True:
            ttl = getattr(request, 'ttl', None)
            username = self._get_username_for_request(username, request)
            try:
                token = self._create_token_for_user(username=username, ttl=ttl)
            except TTLTooLargeException as e:
                abort_request(status_code=http_client.BAD_REQUEST,
                              message=e.message)
                return

            # If remote group sync is enabled, sync the remote groups with local StackStorm roles
            if cfg.CONF.rbac.sync_remote_groups:
                LOG.debug('Retrieving auth backend groups for user "%s"' % (username),
                          extra=extra)
                try:
                    user_groups = self._auth_backend.get_user_groups(username=username)
                except (NotImplementedError, AttributeError):
                    LOG.debug('Configured auth backend doesn\'t expose user group membership '
                              'information, skipping sync...')
                    return token

                if not user_groups:
                    # No groups, return early
                    return token

                extra['username'] = username
                extra['user_groups'] = user_groups

                LOG.debug('Found "%s" groups for user "%s"' % (len(user_groups), username),
                          extra=extra)

                user_db = UserDB(name=username)
                syncer = RBACRemoteGroupToRoleSyncer()

                try:
                    syncer.sync(user_db=user_db, groups=user_groups)
                except Exception as e:
                    # Note: Failed sync is not fatal
                    LOG.exception('Failed to synchronize remote groups for user "%s"' % (username),
                                  extra=extra)
                else:
                    LOG.debug('Successfully synchronized groups for user "%s"' % (username),
                              extra=extra)

                return token
            return token

        LOG.audit('Invalid credentials provided', extra=extra)
        abort_request()


分析:
1.2.1)分析__init__方法
    def __init__(self, *args, **kwargs):
        self._auth_backend = get_backend_instance(name=cfg.CONF.auth.backend)
        super(StandaloneAuthHandler, self).__init__(*args, **kwargs)

因爲這裏cfg.CONF.auth.backend對應keystone,所以這裏採用st2-auth-backend-keystone作爲校驗後端

1.2.2) 分析handle_auth方法
變量打印:
(Pdb) p request
<st2common.router.Body object at 0x7f349bf16790>
(Pdb) p type(request)
<class 'st2common.router.Body'>
(Pdb) p request.__dict__
{}
(Pdb) p headers
{'x-forwarded-for': None}
(Pdb) p remote_addr
'194.16.0.84'
(Pdb) p remote_user
None
(Pdb) p authorization
('Basic', 'bWFfYV9hQGV4YW1wbGUub3JnOm1jMTIzNDU2')
(Pdb) p type(authorization)
<type 'tuple'>
(Pdb) p kwargs
{}
(Pdb) p auth_backend
'KeystoneAuthenticationBackend'
(Pdb) p extra
{'remote_addr': '194.16.0.84', 'auth_backend': 'KeystoneAuthenticationBackend'}
(Pdb) p authorization
('Basic', 'bWFfYV9hQGV4YW1wbGUub3JnOm1jMTIzNDU2')
(Pdb) p auth_type
'Basic'
(Pdb) p auth_value
'bWFfYV9hQGV4YW1wbGUub3JnOm1jMTIzNDU2'
(Pdb) p auth_value
'[email protected]:mc123456'
(Pdb) p split
['[email protected]', 'mc123456']
(Pdb) p username
'[email protected]'
(Pdb) p password
'mc123456'

1.2.3)重點分析
result = self._auth_backend.authenticate(username=username, password=password)
這裏是根據不同的校驗後端進行校驗,進入:
/opt/stackstorm/st2/lib/python2.7/site-packages/st2auth_keystone_backend/keystone.py(66)authenticate()
對於st2-auth-backend-keystone項目的分析具體參見2的分析

2 分析st2-auth-backend-keystone項目
代碼如下:
class KeystoneAuthenticationBackend(object):
    """
    Backend which reads authentication information from keystone

    Note: This backend depends on the "requests" library.
    """

    def __init__(self, keystone_url, keystone_version=2, keystone_mode='username'):
        """
        :param keystone_url: Url of the Keystone server to authenticate against.
        :type keystone_url: ``str``
        :param keystone_version: Keystone version to authenticate against (default to 2).
        :type keystone_version: ``int``
        """
        url = urlparse(keystone_url)
        if url.path != '' or url.query != '' or url.fragment != '':
            raise Exception("The Keystone url {} does not seem to be correct.\n"
                            "Please only set the scheme+url+port "
                            "(e.x.: http://example.com:5000)".format(keystone_url))
        self._keystone_url = keystone_url
        self._keystone_version = keystone_version
        self._keystone_mode = keystone_mode
        self._login = None

    def authenticate(self, username, password, tokenId=None):
        if tokenId:
            creds = self.get_token_creds(tokenId)
            login_url = urljoin(self._keystone_url, 'v3/auth/tokens')
        elif self._keystone_version == 2:
            creds = self._get_v2_creds(username=username, password=password)
            login_url = urljoin(self._keystone_url, 'v2.0/tokens')
        elif self._keystone_version == 3 and self._keystone_mode == 'username':
            creds = self._get_v3_creds(username=username, password=password)
            login_url = urljoin(self._keystone_url, 'v3/auth/tokens')
        elif self._keystone_version == 3 and self._keystone_mode == 'email':
            # judge whether the username is a email or not
            if KeystoneAuthenticationBackend.is_email(username):
                creds = self._get_v3_creds_without_domain(email=username, password=password)
            else:
                creds = self._get_v3_creds_with_username(username=username, password=password)
            login_url = urljoin(self._keystone_url, 'v3/auth/tokens')
        else:
            raise Exception("Keystone version {} not supported".format(self._keystone_version))

        try:
            login = requests.post(login_url, json=creds)
        except Exception as e:
            LOG.debug('Authentication for user "{}" failed: {}'.format(username, str(e)))
            return False

        if login.status_code in [httplib.OK, httplib.CREATED]:
            self.login = login
            LOG.debug('Authentication for user "{}" successful'.format(username))
            return True
        else:
            LOG.debug('Authentication for user "{}" failed: {}'.format(username, login.content))
            return False

分析:
2.1) 分析authenticate(self, username, password, tokenId=None)
邏輯1: 如果存在token,就用token向keystone發送請求去校驗
        if tokenId:
            creds = self.get_token_creds(tokenId)
            login_url = urljoin(self._keystone_url, 'v3/auth/tokens')

    def get_token_creds(self, tokenId):
        creds = {
            "auth": {
                "identity": {
                    "methods": [
                        "token"
                    ],
                    "token": {
                        "id": tokenId
                    }
                }
            }
        }
        return creds

邏輯2: 如果採用用戶名和密碼校驗模式,就用用戶名和密碼向keystone發送請求去校驗
        elif self._keystone_version == 3 and self._keystone_mode == 'username':
            creds = self._get_v3_creds(username=username, password=password)
            login_url = urljoin(self._keystone_url, 'v3/auth/tokens')

    def _get_v3_creds(self, username, password):
        creds = {
            "auth": {
                "identity": {
                    "methods": [
                        "password"
                    ],
                    "password": {
                        "domain": {
                            "id": "default"
                        },
                        "user": {
                            "name": username,
                            "password": password
                        }
                    }
                }
            }
        }
        return creds

邏輯3: 如果採用郵箱校驗模式,區分兩種情況,一種用戶名的確是郵箱,則用郵箱校驗模式;
否則表示採用的是用戶名和密碼去校驗。
        elif self._keystone_version == 3 and self._keystone_mode == 'email':
            # judge whether the username is a email or not
            if KeystoneAuthenticationBackend.is_email(username):
                creds = self._get_v3_creds_without_domain(email=username, password=password)
            else:
                creds = self._get_v3_creds_with_username(username=username, password=password)
            login_url = urljoin(self._keystone_url, 'v3/auth/tokens')
郵箱校驗方式:
    def _get_v3_creds_without_domain(self, email, password):
        creds = {
            "auth": {
                "identity": {
                    "methods": [
                        "password"
                    ],
                    "password": {
                        "user": {
                            "email": email,
                            "password": password
                        }
                    }
                }
            }
        }
        return creds

最終返回結果樣例如下:
(Pdb) p creds
{'auth': {'identity': {'password': {'user': {'password': 'mc123456', 'email': '[email protected]'}}, 'methods': ['password']}}}

(Pdb) p login_url
u'http://keystone-api.openstack.svc.cluster.local:80/v3/auth/tokens'

(Pdb) p login
<Response [201]>
(Pdb) p type(login)
<class 'requests.models.Response'>
(Pdb) p login.__dict__
{'cookies': <<class 'requests.cookies.RequestsCookieJar'>[]>, '_content': '{"token": {"is_domain": false, "methods": ["password"], "roles": [{"id": "6c1afdd223084956aa435b0d6449a364", "name": "admin"}], "expires_at": "2019-08-06T16:48:30.000000Z", "project": {"domain": {"id": "326ecb8104b04d98860ac1d27ac151dc", "name": "ma_depart"}, "id": "7291e8ac053847c9ac3b5ec6d534b499", "name": "ma_project"}, "catalog": [{"endpoints": [{"region_id": "RegionOne", "url": "http://neutron.openstack.svc.cluster.local:80/", "region": "RegionOne", "interface": "public", "id": "1a8df9e8679e44ef868de5890ef8d01d"}, {"region_id": "RegionOne", "url": "http://neutron-server.openstack.svc.cluster.local:9696/", "region": "RegionOne", "interface": "admin", "id": "22fc65e7d1224a048d7edf6a46a65c63"}, {"region_id": "RegionOne", "url": "http://neutron-server.openstack.svc.cluster.local:9696/", "region": "RegionOne", "interface": "internal", "id": "30a19bc8db6e4ca5add713449ab82e19"}], "type": "network", "id": "064be0d031b241ab98d00811dfeb7f2f", "name": "neutron"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://glance-api.openstack.svc.cluster.local:9292/", "region": "RegionOne", "interface": "admin", "id": "8c2499bb92514f539c0cc930bd4d4df0"}, {"region_id": "RegionOne", "url": "http://glance.openstack.svc.cluster.local:80/", "region": "RegionOne", "interface": "public", "id": "a51761dea5a04cf49ac81216ec43d63c"}, {"region_id": "RegionOne", "url": "http://glance-api.openstack.svc.cluster.local:9292/", "region": "RegionOne", "interface": "internal", "id": "e30eaf518b984c94811337a30973b527"}], "type": "image", "id": "2427aac9a32f47d59da9ab2e620bb79f", "name": "glance"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://nova-api.openstack.svc.cluster.local:8774/v2.1/7291e8ac053847c9ac3b5ec6d534b499", "region": "RegionOne", "interface": "admin", "id": "0a5fb8c652c04711a8be44204d299ab3"}, {"region_id": "RegionOne", "url": "http://nova-api.openstack.svc.cluster.local:8774/v2.1/7291e8ac053847c9ac3b5ec6d534b499", "region": "RegionOne", "interface": "internal", "id": "5935300b486a44cca4e02d552c620232"}, {"region_id": "RegionOne", "url": "http://nova.openstack.svc.cluster.local:80/v2.1/7291e8ac053847c9ac3b5ec6d534b499", "region": "RegionOne", "interface": "public", "id": "7f9519c7c8f34662bfc1be839bab94eb"}], "type": "compute", "id": "3058d80a027c4fcab6ed4c240af2db1b", "name": "nova"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://placement-api.openstack.svc.cluster.local:8778/", "region": "RegionOne", "interface": "internal", "id": "295f9b2149b54eafbafac5b7093e5c67"}, {"region_id": "RegionOne", "url": "http://placement.openstack.svc.cluster.local:80/", "region": "RegionOne", "interface": "public", "id": "8aa84bdb2d25492b9246b50167290d79"}, {"region_id": "RegionOne", "url": "http://placement-api.openstack.svc.cluster.local:8778/", "region": "RegionOne", "interface": "admin", "id": "fb5fd9b782094e389ead1c5043b58e50"}], "type": "placement", "id": "3099f8cc159c4aa5a5984e5073778074", "name": "placement"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://keystone-api.openstack.svc.cluster.local:35357/v3", "region": "RegionOne", "interface": "admin", "id": "3ad65c582803478a9b262fa22d4df311"}, {"region_id": "RegionOne", "url": "http://keystone.openstack.svc.cluster.local:80/v3", "region": "RegionOne", "interface": "public", "id": "62a8a10d8c924068987ee0046c53b2e3"}, {"region_id": "RegionOne", "url": "http://keystone-api.openstack.svc.cluster.local:80/v3", "region": "RegionOne", "interface": "internal", "id": "75c6c53dfd00492dbb44bc797ece98b7"}], "type": "identity", "id": "4ecb697033064cadb7fbf139be739062", "name": "keystone"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://cinder-api.openstack.svc.cluster.local:8776/v3/7291e8ac053847c9ac3b5ec6d534b499", "region": "RegionOne", "interface": "internal", "id": "cfe4377d511c4a6eba8896d99f7fa940"}, {"region_id": "RegionOne", "url": "http://cinder-api.openstack.svc.cluster.local:8776/v3/7291e8ac053847c9ac3b5ec6d534b499", "region": "RegionOne", "interface": "admin", "id": "dc2bd224a2454e13acfd6cbbddbb0dad"}, {"region_id": "RegionOne", "url": "http://cinder.openstack.svc.cluster.local:80/v3/7291e8ac053847c9ac3b5ec6d534b499", "region": "RegionOne", "interface": "public", "id": "ef9a317ad6534feda09a0d8f98a07826"}], "type": "volumev3", "id": "516734bce37745a4804af1c505685ca5", "name": "cinder"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://dr-api.openstack.svc.cluster.local:8283/v1/7291e8ac053847c9ac3b5ec6d534b499", "region": "RegionOne", "interface": "admin", "id": "158cd02316de481c8fa1f670bba4be7c"}, {"region_id": "RegionOne", "url": "http://dr.openstack.svc.cluster.local:80/v1/7291e8ac053847c9ac3b5ec6d534b499", "region": "RegionOne", "interface": "public", "id": "d670023fcfc34d5187d73544c71b1ed7"}, {"region_id": "RegionOne", "url": "http://dr-api.openstack.svc.cluster.local:8283/v1/7291e8ac053847c9ac3b5ec6d534b499", "region": "RegionOne", "interface": "internal", "id": "f7f031102433400793d2b27d79b1e6d6"}], "type": "disaster-recovery", "id": "60f94f76a65b4edeaac0c985a3b04bf6", "name": "dr"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://ceilometer-api.openstack.svc.cluster.local:8777/", "region": "RegionOne", "interface": "internal", "id": "3fff73455aa64e2eaecf922ca86e9106"}, {"region_id": "RegionOne", "url": "http://ceilometer.openstack.svc.cluster.local:80/", "region": "RegionOne", "interface": "public", "id": "b7378b542271472f9ad95e03ac47c4f9"}, {"region_id": "RegionOne", "url": "http://ceilometer-api.openstack.svc.cluster.local:8777/", "region": "RegionOne", "interface": "admin", "id": "f15b95444ee344ce82d553617f0d3219"}], "type": "metering", "id": "6e45f56f14324ccab553f628aad58d3d", "name": "ceilometer"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://cinder.openstack.svc.cluster.local:80/v1/7291e8ac053847c9ac3b5ec6d534b499", "region": "RegionOne", "interface": "public", "id": "1c6734e19a0d497fbfa9994d36c57974"}, {"region_id": "RegionOne", "url": "http://cinder-api.openstack.svc.cluster.local:8776/v1/7291e8ac053847c9ac3b5ec6d534b499", "region": "RegionOne", "interface": "internal", "id": "3b414ba896ea4d38a166c40e72098bb2"}, {"region_id": "RegionOne", "url": "http://cinder-api.openstack.svc.cluster.local:8776/v1/7291e8ac053847c9ac3b5ec6d534b499", "region": "RegionOne", "interface": "admin", "id": "fd9095607e2347aea0e3f73651e49913"}], "type": "volume", "id": "7038b7f7289c4203b359e89275c15ef9", "name": "cinder"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://ceph-rgw-ingress.ceph.svc.cluster.local:80/swift/v1", "region": "RegionOne", "interface": "public", "id": "244492db4de94f9cbeec30276a4cb256"}, {"region_id": "RegionOne", "url": "http://ceph-rgw.ceph.svc.cluster.local:8088/swift/v1", "region": "RegionOne", "interface": "internal", "id": "2d35a466627a43429730db6f5f06a117"}, {"region_id": "RegionOne", "url": "http://ceph-rgw.ceph.svc.cluster.local:8088/swift/v1", "region": "RegionOne", "interface": "admin", "id": "5927650b6b7d4bfbbe50457e6849d6cd"}], "type": "object-store", "id": "74c70ce910ea4f95a86b6e144f353a85", "name": "swift"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://aodh.openstack.svc.cluster.local:80/", "region": "RegionOne", "interface": "public", "id": "3c9ad0c9241d403890027cfd2018484e"}, {"region_id": "RegionOne", "url": "http://aodh-api.openstack.svc.cluster.local:8042/", "region": "RegionOne", "interface": "admin", "id": "5b0b9ad4fc7f4ba28e0733a87bc1d5d9"}, {"region_id": "RegionOne", "url": "http://aodh-api.openstack.svc.cluster.local:8042/", "region": "RegionOne", "interface": "internal", "id": "d2b681fa2fa64adc8d7f14fe8c9dc305"}], "type": "alarming", "id": "9457ee74c13340e6a5c699d781dff843", "name": "aodh"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://murano.openstack.svc.cluster.local:80/", "region": "RegionOne", "interface": "public", "id": "8272e029c8bf4bbe9dad8be885b8c53a"}, {"region_id": "RegionOne", "url": "http://murano-api.openstack.svc.cluster.local:8082/", "region": "RegionOne", "interface": "admin", "id": "dd968978e82e497582f4b8e73c0b8404"}, {"region_id": "RegionOne", "url": "http://murano-api.openstack.svc.cluster.local:8082/", "region": "RegionOne", "interface": "internal", "id": "f3a52905195a4f2ca70d5dc583a42207"}], "type": "application-catalog", "id": "9670327356a748b7a26e4ebb04065253", "name": "murano"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://cloudformation.openstack.svc.cluster.local:80/v1", "region": "RegionOne", "interface": "public", "id": "16bd3d28088541d2a7cf6ffdf4cb0252"}, {"region_id": "RegionOne", "url": "http://heat-cfn.openstack.svc.cluster.local:8000/v1", "region": "RegionOne", "interface": "admin", "id": "2c332683c4a84488afdfa70a46cbfdb5"}, {"region_id": "RegionOne", "url": "http://heat-cfn.openstack.svc.cluster.local:8000/v1", "region": "RegionOne", "interface": "internal", "id": "cf0f52e1f6ea4ae98ce1a12c7def42f1"}], "type": "cloudformation", "id": "b6b5412c54504c4d8de7f620630d67a5", "name": "heat-cfn"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://gnocchi-api.openstack.svc.cluster.local:8041/", "region": "RegionOne", "interface": "internal", "id": "26bef4b99c804f0e8f1aeb20e7edb030"}, {"region_id": "RegionOne", "url": "http://gnocchi.openstack.svc.cluster.local:80/", "region": "RegionOne", "interface": "public", "id": "509c83b884ea451985e16eafca8ad1f8"}, {"region_id": "RegionOne", "url": "http://gnocchi-api.openbWFfYV9hQGV4YW1wbGUub3JnOm1jMTIzNDU2stack.svc.cluster.local:8041/", "region": "RegionOne", "interface": "admin", "id": "7aee4f92ac4b43d193b6e7e7943f9d43"}], "type": "metric", "id": "c0e65ed1977c43b38f752bd9cc52b3d5", "name": "gnocchi"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://heat.openstack.svc.cluster.local:80/v1/7291e8ac053847c9ac3b5ec6d534b499", "region": "RegionOne", "interface": "public", "id": "20907d9534eb4e509aff5593d897f150"}, {"region_id": "RegionOne", "url": "http://heat-api.openstack.svc.cluster.local:8004/v1/7291e8ac053847c9ac3b5ec6d534b499", "region": "RegionOne", "interface": "internal", "id": "5b24c509523444baab23a955d1258b21"}, {"region_id": "RegionOne", "url": "http://heat-api.openstack.svc.cluster.local:8004/v1/7291e8ac053847c9ac3b5ec6d534b499", "region": "RegionOne", "interface": "admin", "id": "76782138eb35410bb320a420a03e1201"}], "type": "orchestration", "id": "c1db0b8216e5435cb5592155d4be466f", "name": "heat"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://coaster.openstack.svc.cluster.local:80/", "region": "RegionOne", "interface": "public", "id": "801dd3a16b074808aaa0f0fe3b106510"}, {"region_id": "RegionOne", "url": "http://coaster-all.openstack.svc.cluster.local:8001/", "region": "RegionOne", "interface": "internal", "id": "837fc8a5c9ac43258a1fbf0e3a59abee"}, {"region_id": "RegionOne", "url": "http://coaster-all.openstack.svc.cluster.local:8001/", "region": "RegionOne", "interface": "admin", "id": "f7b1dec9a4ac47cbb56d596a7655c35f"}], "type": "coaster", "id": "e75935f79066498eae03bcaf57c7b686", "name": "coaster"}, {"endpoints": [{"region_id": "RegionOne", "url": "http://cinder-api.openstack.svc.cluster.local:8776/v2/7291e8ac053847c9ac3b5ec6d534b499", "region": "RegionOne", "interface": "internal", "id": "07cd853e446e4194ab98677ee39ea79d"}, {"region_id": "RegionOne", "url": "http://cinder-api.openstack.svc.cluster.local:8776/v2/7291e8ac053847c9ac3b5ec6d534b499", "region": "RegionOne", "interface": "admin", "id": "68fcbf8cd8464fee9fb495cee738735c"}, {"region_id": "RegionOne", "url": "http://cinder.openstack.svc.cluster.local:80/v2/7291e8ac053847c9ac3b5ec6d534b499", "region": "RegionOne", "interface": "public", "id": "7e066f8967a645729a381a4bf198b440"}], "type": "volumev2", "id": "f29469da510b42588be40a3b8e251958", "name": "cinder"}], "user": {"email": "[email protected]", "domain": {"id": "326ecb8104b04d98860ac1d27ac151dc", "name": "ma_depart"}, "id": "6ccfd42a4ac64394836273eb6a7af1c9", "name": "ma_a_a"}, "audit_ids": ["s8K95nfcQKGhUcMPknwT2A"], "issued_at": "2019-08-06T10:48:30.000000Z"}}', 'headers': {'Content-Length': '11948', 'X-Subject-Token': 'gAAAAABdSVr-IvavG4UZPF16Zoq8qF7JA-VmooDxQomzMRJauIf_SrFOcfnQh-YRyQxVvQ13yD93ciRAfTvDXfskvx9ICrk6yaCk583bS7q4IhCe5n0ndo4-byahqMQ5kCVr99sJaESnZlMSqLl932t__JLsCUCpY5xHesPLw_DZ9UEkOSiDQ6E', 'Vary': 'X-Auth-Token', 'Keep-Alive': 'timeout=5, max=100', 'Server': 'Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips mod_wsgi/3.4 Python/2.7.5', 'Connection': 'Keep-Alive', 'Date': 'Tue, 06 Aug 2019 10:48:30 GMT', 'Content-Type': 'application/json', 'x-openstack-request-id': 'req-c9e17d6f-74e6-41a2-868e-53f91090ddac'}, 'url': u'http://keystone-api.openstack.svc.cluster.local:80/v3/auth/tokens', 'status_code': 201, '_content_consumed': True, 'encoding': None, 'request': <PreparedRequest [POST]>, 'connection': <requests.adapters.HTTPAdapter object at 0x5a20910>, 'elapsed': datetime.timedelta(0, 0, 896135), 'raw': <requests.packages.urllib3.response.HTTPResponse object at 0x5799f90>, 'reason': 'Created', 'history': []}

(Pdb) p login.status_code
201

最終返回True。

返回到st2auth服務處代碼繼續分析,具體參見3的分析

3 返回到st2auth服務處代碼繼續分析
class StandaloneAuthHandler(AuthHandlerBase):

    def handle_auth(self, request, headers=None, remote_addr=None, remote_user=None,
                    authorization=None, **kwargs):
      ......
        request_token = self.getTokenId(request)
        result = self._auth_backend.authenticate(username=username,
                                                 password=password,
                                                 tokenId=request_token)

        if result is True:
            ttl = getattr(request, 'ttl', None)
            username = self._get_username_for_request(username, request)
            try:
                token = self._create_token_for_user(username=username, ttl=ttl)
            except TTLTooLargeException as e:
                abort_request(status_code=http_client.BAD_REQUEST,
                              message=e.message)
                return

            # If remote group sync is enabled, sync the remote groups with local StackStorm roles
            if cfg.CONF.rbac.sync_remote_groups:
                LOG.debug('Retrieving auth backend groups for user "%s"' % (username),
                          extra=extra)
                try:
                    user_groups = self._auth_backend.get_user_groups(username=username)
                except (NotImplementedError, AttributeError):
                    LOG.debug('Configured auth backend doesn\'t expose user group membership '
                              'information, skipping sync...')
                    return token

                if not user_groups:
                    # No groups, return early
                    return token

                extra['username'] = username
                extra['user_groups'] = user_groups

                LOG.debug('Found "%s" groups for user "%s"' % (len(user_groups), username),
                          extra=extra)

                user_db = UserDB(name=username)
                syncer = RBACRemoteGroupToRoleSyncer()

                try:
                    syncer.sync(user_db=user_db, groups=user_groups)
                except Exception as e:
                    # Note: Failed sync is not fatal
                    LOG.exception('Failed to synchronize remote groups for user "%s"' % (username),
                                  extra=extra)
                else:
                    LOG.debug('Successfully synchronized groups for user "%s"' % (username),
                              extra=extra)

                return token
            return token

        LOG.audit('Invalid credentials provided', extra=extra)
        abort_request()

分析:
3.1)剛纔分析到
        result = self._auth_backend.authenticate(username=username,
                                                 password=password,
                                                 tokenId=request_token)
這裏採用st2-auth-backend-keystone項目作爲st2的校驗後端,根據不同的校驗模式:
用戶名和密碼模式,或者郵箱模式,或者token模式向keystone發送校驗請求去校驗,如果校驗成功,
則返回True。

3.2) 分析
token = self._create_token_for_user(username=username, ttl=ttl)

def _create_token_for_user(self, username, ttl=None):
    tokendb = create_token(username=username, ttl=ttl)
    return TokenAPI.from_model(tokendb)

 進入: /opt/stackstorm/st2/lib/python2.7/site-packages/st2common/services/access.py(37)create_token()
def create_token(username, ttl=None, metadata=None, add_missing_user=True, service=False):
    if ttl:
        # Note: We allow arbitrary large TTLs for service tokens.
        if not service and ttl > cfg.CONF.auth.token_ttl:
            msg = ('TTL specified %s is greater than max allowed %s.' % (ttl,
                                                                         cfg.CONF.auth.token_ttl))
            raise TTLTooLargeException(msg)
    else:
        ttl = cfg.CONF.auth.token_ttl

    if username:
        try:
            User.get_by_name(username)
        except:
            if add_missing_user:
                user_db = UserDB(name=username)
                User.add_or_update(user_db)

                extra = {'username': username, 'user': user_db}
                LOG.audit('Registered new user "%s".' % (username), extra=extra)
            else:
                raise UserNotFoundError()

    token = uuid.uuid4().hex
    expiry = date_utils.get_datetime_utc_now() + datetime.timedelta(seconds=ttl)
    token = TokenDB(user=username, token=token, expiry=expiry, metadata=metadata, service=service)
    Token.add_or_update(token)

    username_string = username if username else 'an anonymous user'
    token_expire_string = isotime.format(expiry, offset=False)
    extra = {'username': username, 'token_expiration': token_expire_string}

    LOG.audit('Access granted to "%s" with the token set to expire at "%s".' %
              (username_string, token_expire_string), extra=extra)

    return token

分析:
3.2.1)
這裏在校驗成功後,則爲當前用戶創建st2的token(注意和keystone的token是不同的)。
創建token的邏輯流程就是:
獲取token過期時間ttl,默認86400秒,計算過期時間爲當前時間+ttl,採用uuid.uuid4().hex得到token字符串(
例如: '6d177c0a89dd463fa2ae080868c8ed89'),
然後用用戶名,token字符串,過期時間等實例化一個tokendb對象並添加到數據庫中,最後返回該tokendb對象

3.2.2)打印變量
打印create_token方法中變量:
(Pdb) p username
'[email protected]'
(Pdb) p ttl
None
(Pdb) p metadata
None
(Pdb) p add_missing_user
True
(Pdb) p service
False
(Pdb) p username_string
'[email protected]'
(Pdb) p token_expire_string
'2019-08-07T11:23:08.175906Z'
(Pdb) p extra
{'username': '[email protected]', 'token_expiration': '2019-08-07T11:23:08.175906Z'}

返回tokendb對象,形如:
(Pdb) p token
<TokenDB: TokenDB(expiry="2019-08-07 11:23:08.175906+00:00", id=5d4965acc8a3df006e382e73, metadata={}, service=False, token="6d177c0a89dd463fa2ae080868c8ed89", user="[email protected]")>


打印_create_token_for_user方法中變量:
(Pdb) p tokendb
<TokenDB: TokenDB(expiry="2019-08-07 11:23:08.175906+00:00", id=5d4965acc8a3df006e382e73, metadata={}, service=False, token="6d177c0a89dd463fa2ae080868c8ed89", user="[email protected]")>
(Pdb) p type(tokendb)
<class 'st2common.models.db.auth.TokenDB'>
(Pdb) p tokendb.__dict__
{'_cls': 'TokenDB'}


打印變量如下:
(Pdb) p token
TokenAPI(**{'service': False, 'expiry': '2019-08-07T11:23:08.175906Z', 'token': u'6d177c0a89dd463fa2ae080868c8ed89', 'user': u'[email protected]', 'id': '5d4965acc8a3df006e382e73', 'metadata': {}})
(Pdb) p type(token)
<class 'st2common.models.api.auth.TokenAPI'>
(Pdb) p token.__dict__
{'service': False, 'expiry': '2019-08-07T11:23:08.175906Z', 'token': u'6d177c0a89dd463fa2ae080868c8ed89', 'user': u'[email protected]', 'id': '5d4965acc8a3df006e382e73', 'metadata': {}}

3.3) 分析同步rbac權限的代碼
    def handle_auth(self, request, headers=None, remote_addr=None, remote_user=None,
                    authorization=None, **kwargs):
      ......
            # If remote group sync is enabled, sync the remote groups with local StackStorm roles
            if cfg.CONF.rbac.sync_remote_groups:
                LOG.debug('Retrieving auth backend groups for user "%s"' % (username),
                          extra=extra)
                try:
                    user_groups = self._auth_backend.get_user_groups(username=username)
                except (NotImplementedError, AttributeError):
                    LOG.debug('Configured auth backend doesn\'t expose user group membership '
                              'information, skipping sync...')
                    return token

                if not user_groups:
                    # No groups, return early
                    return token

                extra['username'] = username
                extra['user_groups'] = user_groups

                LOG.debug('Found "%s" groups for user "%s"' % (len(user_groups), username),
                          extra=extra)

                user_db = UserDB(name=username)
                syncer = RBACRemoteGroupToRoleSyncer()

                try:
                    syncer.sync(user_db=user_db, groups=user_groups)
                except Exception as e:
                    # Note: Failed sync is not fatal
                    LOG.exception('Failed to synchronize remote groups for user "%s"' % (username),
                                  extra=extra)
                else:
                    LOG.debug('Successfully synchronized groups for user "%s"' % (username),
                              extra=extra)

                return token
            return token

        LOG.audit('Invalid credentials provided', extra=extra)
        abort_request()

3.3.1)
cfg.CONF.rbac.sync_remote_groups爲True
重要的是這一行代碼:
user_groups = self._auth_backend.get_user_groups(username=username)
調用:
/opt/stackstorm/st2/lib/python2.7/site-packages/st2auth_keystone_backend/keystone.py的get_user_groups()
對應代碼如下:
class KeystoneAuthenticationBackend(object):

    def get_user_groups(self, username):
        groups = []
        roles = self._get_role_assignment()
        LOG.debug('Keystone roles are: %s', roles)
        for r in roles["role_assignments"]:
            if r['scope'].get('domain', None):
                roleName = r["role"]["name"]
                if '_member_' == roleName:
                    roleName = 'Member'
                groups.append(roleName)
        return groups

分析:
(Pdb) p username
'[email protected]'

調用:
    def _get_role_assignment(self):
        content = json.loads(self.login.content)
        headers = self.login.headers
        user_id = content['token']['user']['id']
        token = headers['X-Subject-Token']
        parsed_url = urlsplit(self._keystone_url)
        url = urlunsplit([parsed_url.scheme,
                          parsed_url.netloc,
                          'v3/role_assignments',
                          'user.id=' + user_id, parsed_url.fragment])
        headers = {}
        headers['X-Auth-Token'] = token
        resp = requests.get(url, headers=headers)
        return json.loads(resp.content)

分析:
(Pdb) p content
{u'token': {u'is_domain': False, u'methods': [u'password'], u'roles': [{u'id': u'6c1afdd223084956aa435b0d6449a364', u'name': u'admin'}], u'expires_at': u'2019-08-06T16:48:30.000000Z', u'project': {u'domain': {u'id': u'326ecb8104b04d98860ac1d27ac151dc', u'name': u'ma_depart'}, u'id': u'7291e8ac053847c9ac3b5ec6d534b499', u'name': u'ma_project'}, u'catalog': [{u'endpoints': [{u'url': u'http://neutron.openstack.svc.cluster.local:80/', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'1a8df9e8679e44ef868de5890ef8d01d'}, {u'url': u'http://neutron-server.openstack.svc.cluster.local:9696/', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'22fc65e7d1224a048d7edf6a46a65c63'}, {u'url': u'http://neutron-server.openstack.svc.cluster.local:9696/', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'30a19bc8db6e4ca5add713449ab82e19'}], u'type': u'network', u'id': u'064be0d031b241ab98d00811dfeb7f2f', u'name': u'neutron'}, {u'endpoints': [{u'url': u'http://glance-api.openstack.svc.cluster.local:9292/', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'8c2499bb92514f539c0cc930bd4d4df0'}, {u'url': u'http://glance.openstack.svc.cluster.local:80/', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'a51761dea5a04cf49ac81216ec43d63c'}, {u'url': u'http://glance-api.openstack.svc.cluster.local:9292/', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'e30eaf518b984c94811337a30973b527'}], u'type': u'image', u'id': u'2427aac9a32f47d59da9ab2e620bb79f', u'name': u'glance'}, {u'endpoints': [{u'url': u'http://nova-api.openstack.svc.cluster.local:8774/v2.1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'0a5fb8c652c04711a8be44204d299ab3'}, {u'url': u'http://nova-api.openstack.svc.cluster.local:8774/v2.1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'5935300b486a44cca4e02d552c620232'}, {u'url': u'http://nova.openstack.svc.cluster.local:80/v2.1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'7f9519c7c8f34662bfc1be839bab94eb'}], u'type': u'compute', u'id': u'3058d80a027c4fcab6ed4c240af2db1b', u'name': u'nova'}, {u'endpoints': [{u'url': u'http://placement-api.openstack.svc.cluster.local:8778/', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'295f9b2149b54eafbafac5b7093e5c67'}, {u'url': u'http://placement.openstack.svc.cluster.local:80/', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'8aa84bdb2d25492b9246b50167290d79'}, {u'url': u'http://placement-api.openstack.svc.cluster.local:8778/', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'fb5fd9b782094e389ead1c5043b58e50'}], u'type': u'placement', u'id': u'3099f8cc159c4aa5a5984e5073778074', u'name': u'placement'}, {u'endpoints': [{u'url': u'http://keystone-api.openstack.svc.cluster.local:35357/v3', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'3ad65c582803478a9b262fa22d4df311'}, {u'url': u'http://keystone.openstack.svc.cluster.local:80/v3', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'62a8a10d8c924068987ee0046c53b2e3'}, {u'url': u'http://keystone-api.openstack.svc.cluster.local:80/v3', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'75c6c53dfd00492dbb44bc797ece98b7'}], u'type': u'identity', u'id': u'4ecb697033064cadb7fbf139be739062', u'name': u'keystone'}, {u'endpoints': [{u'url': u'http://cinder-api.openstack.svc.cluster.local:8776/v3/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'cfe4377d511c4a6eba8896d99f7fa940'}, {u'url': u'http://cinder-api.openstack.svc.cluster.local:8776/v3/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'dc2bd224a2454e13acfd6cbbddbb0dad'}, {u'url': u'http://cinder.openstack.svc.cluster.local:80/v3/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'ef9a317ad6534feda09a0d8f98a07826'}], u'type': u'volumev3', u'id': u'516734bce37745a4804af1c505685ca5', u'name': u'cinder'}, {u'endpoints': [{u'url': u'http://dr-api.openstack.svc.cluster.local:8283/v1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'158cd02316de481c8fa1f670bba4be7c'}, {u'url': u'http://dr.openstack.svc.cluster.local:80/v1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'd670023fcfc34d5187d73544c71b1ed7'}, {u'url': u'http://dr-api.openstack.svc.cluster.local:8283/v1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'f7f031102433400793d2b27d79b1e6d6'}], u'type': u'disaster-recovery', u'id': u'60f94f76a65b4edeaac0c985a3b04bf6', u'name': u'dr'}, {u'endpoints': [{u'url': u'http://ceilometer-api.openstack.svc.cluster.local:8777/', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'3fff73455aa64e2eaecf922ca86e9106'}, {u'url': u'http://ceilometer.openstack.svc.cluster.local:80/', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'b7378b542271472f9ad95e03ac47c4f9'}, {u'url': u'http://ceilometer-api.openstack.svc.cluster.local:8777/', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'f15b95444ee344ce82d553617f0d3219'}], u'type': u'metering', u'id': u'6e45f56f14324ccab553f628aad58d3d', u'name': u'ceilometer'}, {u'endpoints': [{u'url': u'http://cinder.openstack.svc.cluster.local:80/v1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'1c6734e19a0d497fbfa9994d36c57974'}, {u'url': u'http://cinder-api.openstack.svc.cluster.local:8776/v1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'3b414ba896ea4d38a166c40e72098bb2'}, {u'url': u'http://cinder-api.openstack.svc.cluster.local:8776/v1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'fd9095607e2347aea0e3f73651e49913'}], u'type': u'volume', u'id': u'7038b7f7289c4203b359e89275c15ef9', u'name': u'cinder'}, {u'endpoints': [{u'url': u'http://ceph-rgw-ingress.ceph.svc.cluster.local:80/swift/v1', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'244492db4de94f9cbeec30276a4cb256'}, {u'url': u'http://ceph-rgw.ceph.svc.cluster.local:8088/swift/v1', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'2d35a466627a43429730db6f5f06a117'}, {u'url': u'http://ceph-rgw.ceph.svc.cluster.local:8088/swift/v1', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'5927650b6b7d4bfbbe50457e6849d6cd'}], u'type': u'object-store', u'id': u'74c70ce910ea4f95a86b6e144f353a85', u'name': u'swift'}, {u'endpoints': [{u'url': u'http://aodh.openstack.svc.cluster.local:80/', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'3c9ad0c9241d403890027cfd2018484e'}, {u'url': u'http://aodh-api.openstack.svc.cluster.local:8042/', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'5b0b9ad4fc7f4ba28e0733a87bc1d5d9'}, {u'url': u'http://aodh-api.openstack.svc.cluster.local:8042/', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'd2b681fa2fa64adc8d7f14fe8c9dc305'}], u'type': u'alarming', u'id': u'9457ee74c13340e6a5c699d781dff843', u'name': u'aodh'}, {u'endpoints': [{u'url': u'http://murano.openstack.svc.cluster.local:80/', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'8272e029c8bf4bbe9dad8be885b8c53a'}, {u'url': u'http://murano-api.openstack.svc.cluster.local:8082/', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'dd968978e82e497582f4b8e73c0b8404'}, {u'url': u'http://murano-api.openstack.svc.cluster.local:8082/', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'f3a52905195a4f2ca70d5dc583a42207'}], u'type': u'application-catalog', u'id': u'9670327356a748b7a26e4ebb04065253', u'name': u'murano'}, {u'endpoints': [{u'url': u'http://cloudformation.openstack.svc.cluster.local:80/v1', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'16bd3d28088541d2a7cf6ffdf4cb0252'}, {u'url': u'http://heat-cfn.openstack.svc.cluster.local:8000/v1', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'2c332683c4a84488afdfa70a46cbfdb5'}, {u'url': u'http://heat-cfn.openstack.svc.cluster.local:8000/v1', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'cf0f52e1f6ea4ae98ce1a12c7def42f1'}], u'type': u'cloudformation', u'id': u'b6b5412c54504c4d8de7f620630d67a5', u'name': u'heat-cfn'}, {u'endpoints': [{u'url': u'http://gnocchi-api.openstack.svc.cluster.local:8041/', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'26bef4b99c804f0e8f1aeb20e7edb030'}, {u'url': u'http://gnocchi.openstack.svc.cluster.local:80/', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'509c83b884ea451985e16eafca8ad1f8'}, {u'url': u'http://gnocchi-api.openstack.svc.cluster.local:8041/', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'7aee4f92ac4b43d193b6e7e7943f9d43'}], u'type': u'metric', u'id': u'c0e65ed1977c43b38f752bd9cc52b3d5', u'name': u'gnocchi'}, {u'endpoints': [{u'url': u'http://heat.openstack.svc.cluster.local:80/v1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'20907d9534eb4e509aff5593d897f150'}, {u'url': u'http://heat-api.openstack.svc.cluster.local:8004/v1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'5b24c509523444baab23a955d1258b21'}, {u'url': u'http://heat-api.openstack.svc.cluster.local:8004/v1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'76782138eb35410bb320a420a03e1201'}], u'type': u'orchestration', u'id': u'c1db0b8216e5435cb5592155d4be466f', u'name': u'heat'}, {u'endpoints': [{u'url': u'http://coaster.openstack.svc.cluster.local:80/', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'801dd3a16b074808aaa0f0fe3b106510'}, {u'url': u'http://coaster-all.openstack.svc.cluster.local:8001/', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'837fc8a5c9ac43258a1fbf0e3a59abee'}, {u'url': u'http://coaster-all.openstack.svc.cluster.local:8001/', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'f7b1dec9a4ac47cbb56d596a7655c35f'}], u'type': u'coaster', u'id': u'e75935f79066498eae03bcaf57c7b686', u'name': u'coaster'}, {u'endpoints': [{u'url': u'http://cinder-api.openstack.svc.cluster.local:8776/v2/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'07cd853e446e4194ab98677ee39ea79d'}, {u'url': u'http://cinder-api.openstack.svc.cluster.local:8776/v2/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'68fcbf8cd8464fee9fb495cee738735c'}, {u'url': u'http://cinder.openstack.svc.cluster.local:80/v2/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'7e066f8967a645729a381a4bf198b440'}], u'type': u'volumev2', u'id': u'f29469da510b42588be40a3b8e251958', u'name': u'cinder'}], u'user': {u'domain': {u'id': u'326ecb8104b04d98860ac1d27ac151dc', u'name': u'ma_depart'}, u'email': u'[email protected]', u'name': u'ma_a_a', u'id': u'6ccfd42a4ac64394836273eb6a7af1c9'}, u'audit_ids': [u's8K95nfcQKGhUcMPknwT2A'], u'issued_at': u'2019-08-06T10:48:30.000000Z'}}

(Pdb) p headers
{'Content-Length': '11948', 'X-Subject-Token': 'gAAAAABdSVr-IvavG4UZPF16Zoq8qF7JA-VmooDxQomzMRJauIf_SrFOcfnQh-YRyQxVvQ13yD93ciRAfTvDXfskvx9ICrk6yaCk583bS7q4IhCe5n0ndo4-byahqMQ5kCVr99sJaESnZlMSqLl932t__JLsCUCpY5xHesPLw_DZ9UEkOSiDQ6E', 'Vary': 'X-Auth-Token', 'Keep-Alive': 'timeout=5, max=100', 'Server': 'Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips mod_wsgi/3.4 Python/2.7.5', 'Connection': 'Keep-Alive', 'Date': 'Tue, 06 Aug 2019 10:48:30 GMT', 'Content-Type': 'application/json', 'x-openstack-request-id': 'req-c9e17d6f-74e6-41a2-868e-53f91090ddac'}
(Pdb) p type(headers)
<class 'requests.structures.CaseInsensitiveDict'>

(Pdb) p content['token']
{u'is_domain': False, u'methods': [u'password'], u'roles': [{u'id': u'6c1afdd223084956aa435b0d6449a364', u'name': u'admin'}], u'expires_at': u'2019-08-06T16:48:30.000000Z', u'project': {u'domain': {u'id': u'326ecb8104b04d98860ac1d27ac151dc', u'name': u'ma_depart'}, u'id': u'7291e8ac053847c9ac3b5ec6d534b499', u'name': u'ma_project'}, u'catalog': [{u'endpoints': [{u'url': u'http://neutron.openstack.svc.cluster.local:80/', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'1a8df9e8679e44ef868de5890ef8d01d'}, {u'url': u'http://neutron-server.openstack.svc.cluster.local:9696/', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'22fc65e7d1224a048d7edf6a46a65c63'}, {u'url': u'http://neutron-server.openstack.svc.cluster.local:9696/', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'30a19bc8db6e4ca5add713449ab82e19'}], u'type': u'network', u'id': u'064be0d031b241ab98d00811dfeb7f2f', u'name': u'neutron'}, {u'endpoints': [{u'url': u'http://glance-api.openstack.svc.cluster.local:9292/', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'8c2499bb92514f539c0cc930bd4d4df0'}, {u'url': u'http://glance.openstack.svc.cluster.local:80/', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'a51761dea5a04cf49ac81216ec43d63c'}, {u'url': u'http://glance-api.openstack.svc.cluster.local:9292/', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'e30eaf518b984c94811337a30973b527'}], u'type': u'image', u'id': u'2427aac9a32f47d59da9ab2e620bb79f', u'name': u'glance'}, {u'endpoints': [{u'url': u'http://nova-api.openstack.svc.cluster.local:8774/v2.1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'0a5fb8c652c04711a8be44204d299ab3'}, {u'url': u'http://nova-api.openstack.svc.cluster.local:8774/v2.1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'5935300b486a44cca4e02d552c620232'}, {u'url': u'http://nova.openstack.svc.cluster.local:80/v2.1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'7f9519c7c8f34662bfc1be839bab94eb'}], u'type': u'compute', u'id': u'3058d80a027c4fcab6ed4c240af2db1b', u'name': u'nova'}, {u'endpoints': [{u'url': u'http://placement-api.openstack.svc.cluster.local:8778/', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'295f9b2149b54eafbafac5b7093e5c67'}, {u'url': u'http://placement.openstack.svc.cluster.local:80/', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'8aa84bdb2d25492b9246b50167290d79'}, {u'url': u'http://placement-api.openstack.svc.cluster.local:8778/', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'fb5fd9b782094e389ead1c5043b58e50'}], u'type': u'placement', u'id': u'3099f8cc159c4aa5a5984e5073778074', u'name': u'placement'}, {u'endpoints': [{u'url': u'http://keystone-api.openstack.svc.cluster.local:35357/v3', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'3ad65c582803478a9b262fa22d4df311'}, {u'url': u'http://keystone.openstack.svc.cluster.local:80/v3', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'62a8a10d8c924068987ee0046c53b2e3'}, {u'url': u'http://keystone-api.openstack.svc.cluster.local:80/v3', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'75c6c53dfd00492dbb44bc797ece98b7'}], u'type': u'identity', u'id': u'4ecb697033064cadb7fbf139be739062', u'name': u'keystone'}, {u'endpoints': [{u'url': u'http://cinder-api.openstack.svc.cluster.local:8776/v3/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'cfe4377d511c4a6eba8896d99f7fa940'}, {u'url': u'http://cinder-api.openstack.svc.cluster.local:8776/v3/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'dc2bd224a2454e13acfd6cbbddbb0dad'}, {u'url': u'http://cinder.openstack.svc.cluster.local:80/v3/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'ef9a317ad6534feda09a0d8f98a07826'}], u'type': u'volumev3', u'id': u'516734bce37745a4804af1c505685ca5', u'name': u'cinder'}, {u'endpoints': [{u'url': u'http://dr-api.openstack.svc.cluster.local:8283/v1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'158cd02316de481c8fa1f670bba4be7c'}, {u'url': u'http://dr.openstack.svc.cluster.local:80/v1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'd670023fcfc34d5187d73544c71b1ed7'}, {u'url': u'http://dr-api.openstack.svc.cluster.local:8283/v1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'f7f031102433400793d2b27d79b1e6d6'}], u'type': u'disaster-recovery', u'id': u'60f94f76a65b4edeaac0c985a3b04bf6', u'name': u'dr'}, {u'endpoints': [{u'url': u'http://ceilometer-api.openstack.svc.cluster.local:8777/', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'3fff73455aa64e2eaecf922ca86e9106'}, {u'url': u'http://ceilometer.openstack.svc.cluster.local:80/', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'b7378b542271472f9ad95e03ac47c4f9'}, {u'url': u'http://ceilometer-api.openstack.svc.cluster.local:8777/', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'f15b95444ee344ce82d553617f0d3219'}], u'type': u'metering', u'id': u'6e45f56f14324ccab553f628aad58d3d', u'name': u'ceilometer'}, {u'endpoints': [{u'url': u'http://cinder.openstack.svc.cluster.local:80/v1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'1c6734e19a0d497fbfa9994d36c57974'}, {u'url': u'http://cinder-api.openstack.svc.cluster.local:8776/v1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'3b414ba896ea4d38a166c40e72098bb2'}, {u'url': u'http://cinder-api.openstack.svc.cluster.local:8776/v1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'fd9095607e2347aea0e3f73651e49913'}], u'type': u'volume', u'id': u'7038b7f7289c4203b359e89275c15ef9', u'name': u'cinder'}, {u'endpoints': [{u'url': u'http://ceph-rgw-ingress.ceph.svc.cluster.local:80/swift/v1', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'244492db4de94f9cbeec30276a4cb256'}, {u'url': u'http://ceph-rgw.ceph.svc.cluster.local:8088/swift/v1', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'2d35a466627a43429730db6f5f06a117'}, {u'url': u'http://ceph-rgw.ceph.svc.cluster.local:8088/swift/v1', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'5927650b6b7d4bfbbe50457e6849d6cd'}], u'type': u'object-store', u'id': u'74c70ce910ea4f95a86b6e144f353a85', u'name': u'swift'}, {u'endpoints': [{u'url': u'http://aodh.openstack.svc.cluster.local:80/', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'3c9ad0c9241d403890027cfd2018484e'}, {u'url': u'http://aodh-api.openstack.svc.cluster.local:8042/', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'5b0b9ad4fc7f4ba28e0733a87bc1d5d9'}, {u'url': u'http://aodh-api.openstack.svc.cluster.local:8042/', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'd2b681fa2fa64adc8d7f14fe8c9dc305'}], u'type': u'alarming', u'id': u'9457ee74c13340e6a5c699d781dff843', u'name': u'aodh'}, {u'endpoints': [{u'url': u'http://murano.openstack.svc.cluster.local:80/', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'8272e029c8bf4bbe9dad8be885b8c53a'}, {u'url': u'http://murano-api.openstack.svc.cluster.local:8082/', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'dd968978e82e497582f4b8e73c0b8404'}, {u'url': u'http://murano-api.openstack.svc.cluster.local:8082/', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'f3a52905195a4f2ca70d5dc583a42207'}], u'type': u'application-catalog', u'id': u'9670327356a748b7a26e4ebb04065253', u'name': u'murano'}, {u'endpoints': [{u'url': u'http://cloudformation.openstack.svc.cluster.local:80/v1', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'16bd3d28088541d2a7cf6ffdf4cb0252'}, {u'url': u'http://heat-cfn.openstack.svc.cluster.local:8000/v1', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'2c332683c4a84488afdfa70a46cbfdb5'}, {u'url': u'http://heat-cfn.openstack.svc.cluster.local:8000/v1', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'cf0f52e1f6ea4ae98ce1a12c7def42f1'}], u'type': u'cloudformation', u'id': u'b6b5412c54504c4d8de7f620630d67a5', u'name': u'heat-cfn'}, {u'endpoints': [{u'url': u'http://gnocchi-api.openstack.svc.cluster.local:8041/', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'26bef4b99c804f0e8f1aeb20e7edb030'}, {u'url': u'http://gnocchi.openstack.svc.cluster.local:80/', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'509c83b884ea451985e16eafca8ad1f8'}, {u'url': u'http://gnocchi-api.openstack.svc.cluster.local:8041/', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'7aee4f92ac4b43d193b6e7e7943f9d43'}], u'type': u'metric', u'id': u'c0e65ed1977c43b38f752bd9cc52b3d5', u'name': u'gnocchi'}, {u'endpoints': [{u'url': u'http://heat.openstack.svc.cluster.local:80/v1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'20907d9534eb4e509aff5593d897f150'}, {u'url': u'http://heat-api.openstack.svc.cluster.local:8004/v1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'5b24c509523444baab23a955d1258b21'}, {u'url': u'http://heat-api.openstack.svc.cluster.local:8004/v1/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'76782138eb35410bb320a420a03e1201'}], u'type': u'orchestration', u'id': u'c1db0b8216e5435cb5592155d4be466f', u'name': u'heat'}, {u'endpoints': [{u'url': u'http://coaster.openstack.svc.cluster.local:80/', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'801dd3a16b074808aaa0f0fe3b106510'}, {u'url': u'http://coaster-all.openstack.svc.cluster.local:8001/', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'837fc8a5c9ac43258a1fbf0e3a59abee'}, {u'url': u'http://coaster-all.openstack.svc.cluster.local:8001/', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'f7b1dec9a4ac47cbb56d596a7655c35f'}], u'type': u'coaster', u'id': u'e75935f79066498eae03bcaf57c7b686', u'name': u'coaster'}, {u'endpoints': [{u'url': u'http://cinder-api.openstack.svc.cluster.local:8776/v2/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'internal', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'07cd853e446e4194ab98677ee39ea79d'}, {u'url': u'http://cinder-api.openstack.svc.cluster.local:8776/v2/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'admin', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'68fcbf8cd8464fee9fb495cee738735c'}, {u'url': u'http://cinder.openstack.svc.cluster.local:80/v2/7291e8ac053847c9ac3b5ec6d534b499', u'interface': u'public', u'region': u'RegionOne', u'region_id': u'RegionOne', u'id': u'7e066f8967a645729a381a4bf198b440'}], u'type': u'volumev2', u'id': u'f29469da510b42588be40a3b8e251958', u'name': u'cinder'}], u'user': {u'domain': {u'id': u'326ecb8104b04d98860ac1d27ac151dc', u'name': u'ma_depart'}, u'email': u'[email protected]', u'name': u'ma_a_a', u'id': u'6ccfd42a4ac64394836273eb6a7af1c9'}, u'audit_ids': [u's8K95nfcQKGhUcMPknwT2A'], u'issued_at': u'2019-08-06T10:48:30.000000Z'}

(Pdb) p content['token']['user']
{u'domain': {u'id': u'326ecb8104b04d98860ac1d27ac151dc', u'name': u'ma_depart'}, u'email': u'[email protected]', u'name': u'ma_a_a', u'id': u'6ccfd42a4ac64394836273eb6a7af1c9'}

(Pdb) p content['token']['user']['id']
u'6ccfd42a4ac64394836273eb6a7af1c9'

(Pdb) p token
'gAAAAABdSVr-IvavG4UZPF16Zoq8qF7JA-VmooDxQomzMRJauIf_SrFOcfnQh-YRyQxVvQ13yD93ciRAfTvDXfskvx9ICrk6yaCk583bS7q4IhCe5n0ndo4-byahqMQ5kCVr99sJaESnZlMSqLl932t__JLsCUCpY5xHesPLw_DZ9UEkOSiDQ6E'

(Pdb) p self._keystone_url
u'http://keystone-api.openstack.svc.cluster.local:80'
(Pdb) p self
<st2auth_keystone_backend.keystone.KeystoneAuthenticationBackend object at 0x5c3e090>
(Pdb) p self.__dict__
{'_login': None, 'login': <Response [201]>, '_keystone_version': 3, '_keystone_url': u'http://keystone-api.openstack.svc.cluster.local:80', '_keystone_mode': u'email'}

(Pdb) p parsed_url
SplitResult(scheme=u'http', netloc=u'keystone-api.openstack.svc.cluster.local:80', path=u'', query='', fragment='')
(Pdb) p type(parsed_url)
<class 'urlparse.SplitResult'>

(Pdb) p parsed_url.scheme
u'http'
(Pdb) p parsed_url.netloc
u'keystone-api.openstack.svc.cluster.local:80'

(Pdb) p url
u'http://keystone-api.openstack.svc.cluster.local:80/v3/role_assignments?user.id=6ccfd42a4ac64394836273eb6a7af1c9'
(Pdb) p type(url)
<type 'unicode'>

(Pdb) p resp
<Response [200]>
(Pdb) p type(resp)
<class 'requests.models.Response'>
(Pdb) p resp.__dict__
{'cookies': <<class 'requests.cookies.RequestsCookieJar'>[]>, '_content': '{"role_assignments": [{"scope": {"project": {"id": "7291e8ac053847c9ac3b5ec6d534b499", "name": "ma_project"}}, "role": {"id": "6c1afdd223084956aa435b0d6449a364", "name": "admin"}, "user": {"id": "6ccfd42a4ac64394836273eb6a7af1c9", "name": "ma_a_a"}, "links": {"assignment": "http://keystone-api.openstack.svc.cluster.local/v3/projects/7291e8ac053847c9ac3b5ec6d534b499/users/6ccfd42a4ac64394836273eb6a7af1c9/roles/6c1afdd223084956aa435b0d6449a364"}}, {"scope": {"domain": {"id": "326ecb8104b04d98860ac1d27ac151dc", "name": "ma_depart"}}, "role": {"id": "6c1afdd223084956aa435b0d6449a364", "name": "admin"}, "user": {"id": "6ccfd42a4ac64394836273eb6a7af1c9", "name": "ma_a_a"}, "links": {"assignment": "http://keystone-api.openstack.svc.cluster.local/v3/domains/326ecb8104b04d98860ac1d27ac151dc/users/6ccfd42a4ac64394836273eb6a7af1c9/roles/6c1afdd223084956aa435b0d6449a364"}}], "links": {"self": "http://keystone-api.openstack.svc.cluster.local/v3/role_assignments?user.id=6ccfd42a4ac64394836273eb6a7af1c9", "previous": null, "next": null}}', 'headers': {'Content-Length': '1038', 'Vary': 'X-Auth-Token', 'Keep-Alive': 'timeout=5, max=100', 'Server': 'Apache/2.4.6 (CentOS) OpenSSL/1.0.2k-fips mod_wsgi/3.4 Python/2.7.5', 'Connection': 'Keep-Alive', 'Date': 'Tue, 06 Aug 2019 12:20:48 GMT', 'Content-Type': 'application/json', 'x-openstack-request-id': 'req-e72766e9-d1c9-44a8-821c-4276cac0c97a'}, 'url': u'http://keystone-api.openstack.svc.cluster.local:80/v3/role_assignments?user.id=6ccfd42a4ac64394836273eb6a7af1c9', 'status_code': 200, '_content_consumed': True, 'encoding': None, 'request': <PreparedRequest [GET]>, 'connection': <requests.adapters.HTTPAdapter object at 0x532c750>, 'elapsed': datetime.timedelta(0, 0, 329416), 'raw': <requests.packages.urllib3.response.HTTPResponse object at 0x532cf50>, 'reason': 'OK', 'history': []}
(Pdb) 

(Pdb) p resp.content
'{"role_assignments": [{"scope": {"project": {"id": "7291e8ac053847c9ac3b5ec6d534b499", "name": "ma_project"}}, "role": {"id": "6c1afdd223084956aa435b0d6449a364", "name": "admin"}, "user": {"id": "6ccfd42a4ac64394836273eb6a7af1c9", "name": "ma_a_a"}, "links": {"assignment": "http://keystone-api.openstack.svc.cluster.local/v3/projects/7291e8ac053847c9ac3b5ec6d534b499/users/6ccfd42a4ac64394836273eb6a7af1c9/roles/6c1afdd223084956aa435b0d6449a364"}}, {"scope": {"domain": {"id": "326ecb8104b04d98860ac1d27ac151dc", "name": "ma_depart"}}, "role": {"id": "6c1afdd223084956aa435b0d6449a364", "name": "admin"}, "user": {"id": "6ccfd42a4ac64394836273eb6a7af1c9", "name": "ma_a_a"}, "links": {"assignment": "http://keystone-api.openstack.svc.cluster.local/v3/domains/326ecb8104b04d98860ac1d27ac151dc/users/6ccfd42a4ac64394836273eb6a7af1c9/roles/6c1afdd223084956aa435b0d6449a364"}}], "links": {"self": "http://keystone-api.openstack.svc.cluster.local/v3/role_assignments?user.id=6ccfd42a4ac64394836273eb6a7af1c9", "previous": null, "next": null}}'

(Pdb) p roles
{u'role_assignments': [{u'scope': {u'project': {u'id': u'7291e8ac053847c9ac3b5ec6d534b499', u'name': u'ma_project'}}, u'role': {u'id': u'6c1afdd223084956aa435b0d6449a364', u'name': u'admin'}, u'user': {u'id': u'6ccfd42a4ac64394836273eb6a7af1c9', u'name': u'ma_a_a'}, u'links': {u'assignment': u'http://keystone-api.openstack.svc.cluster.local/v3/projects/7291e8ac053847c9ac3b5ec6d534b499/users/6ccfd42a4ac64394836273eb6a7af1c9/roles/6c1afdd223084956aa435b0d6449a364'}}, {u'scope': {u'domain': {u'id': u'326ecb8104b04d98860ac1d27ac151dc', u'name': u'ma_depart'}}, u'role': {u'id': u'6c1afdd223084956aa435b0d6449a364', u'name': u'admin'}, u'user': {u'id': u'6ccfd42a4ac64394836273eb6a7af1c9', u'name': u'ma_a_a'}, u'links': {u'assignment': u'http://keystone-api.openstack.svc.cluster.local/v3/domains/326ecb8104b04d98860ac1d27ac151dc/users/6ccfd42a4ac64394836273eb6a7af1c9/roles/6c1afdd223084956aa435b0d6449a364'}}], u'links': {u'self': u'http://keystone-api.openstack.svc.cluster.local/v3/role_assignments?user.id=6ccfd42a4ac64394836273eb6a7af1c9', u'next': None, u'previous': None}}

(Pdb) p roles["role_assignments"]
[{u'scope': {u'project': {u'id': u'7291e8ac053847c9ac3b5ec6d534b499', u'name': u'ma_project'}}, u'role': {u'id': u'6c1afdd223084956aa435b0d6449a364', u'name': u'admin'}, u'user': {u'id': u'6ccfd42a4ac64394836273eb6a7af1c9', u'name': u'ma_a_a'}, u'links': {u'assignment': u'http://keystone-api.openstack.svc.cluster.local/v3/projects/7291e8ac053847c9ac3b5ec6d534b499/users/6ccfd42a4ac64394836273eb6a7af1c9/roles/6c1afdd223084956aa435b0d6449a364'}}, {u'scope': {u'domain': {u'id': u'326ecb8104b04d98860ac1d27ac151dc', u'name': u'ma_depart'}}, u'role': {u'id': u'6c1afdd223084956aa435b0d6449a364', u'name': u'admin'}, u'user': {u'id': u'6ccfd42a4ac64394836273eb6a7af1c9', u'name': u'ma_a_a'}, u'links': {u'assignment': u'http://keystone-api.openstack.svc.cluster.local/v3/domains/326ecb8104b04d98860ac1d27ac151dc/users/6ccfd42a4ac64394836273eb6a7af1c9/roles/6c1afdd223084956aa435b0d6449a364'}}]

循環遍歷role
第一次循環:
(Pdb) p r
{u'scope': {u'project': {u'id': u'7291e8ac053847c9ac3b5ec6d534b499', u'name': u'ma_project'}}, u'role': {u'id': u'6c1afdd223084956aa435b0d6449a364', u'name': u'admin'}, u'user': {u'id': u'6ccfd42a4ac64394836273eb6a7af1c9', u'name': u'ma_a_a'}, u'links': {u'assignment': u'http://keystone-api.openstack.svc.cluster.local/v3/projects/7291e8ac053847c9ac3b5ec6d534b499/users/6ccfd42a4ac64394836273eb6a7af1c9/roles/6c1afdd223084956aa435b0d6449a364'}}
(Pdb) p r['scope']
{u'project': {u'id': u'7291e8ac053847c9ac3b5ec6d534b499', u'name': u'ma_project'}}
(Pdb) p r['scope'].get('domain', None)
None

第二次循環:
(Pdb) p r
{u'scope': {u'domain': {u'id': u'326ecb8104b04d98860ac1d27ac151dc', u'name': u'ma_depart'}}, u'role': {u'id': u'6c1afdd223084956aa435b0d6449a364', u'name': u'admin'}, u'user': {u'id': u'6ccfd42a4ac64394836273eb6a7af1c9', u'name': u'ma_a_a'}, u'links': {u'assignment': u'http://keystone-api.openstack.svc.cluster.local/v3/domains/326ecb8104b04d98860ac1d27ac151dc/users/6ccfd42a4ac64394836273eb6a7af1c9/roles/6c1afdd223084956aa435b0d6449a364'}}
(Pdb) p r['scope']
{u'domain': {u'id': u'326ecb8104b04d98860ac1d27ac151dc', u'name': u'ma_depart'}}
(Pdb) p r['scope'].get('domain', None)
{u'id': u'326ecb8104b04d98860ac1d27ac151dc', u'name': u'ma_depart'}
(Pdb) p r["role"]["name"]
u'admin'
(Pdb) p groups
[u'admin']

最終返回的是:
(Pdb) p user_groups
[u'admin']

3.4) 返回繼續分析同步rbac權限的代碼
    def handle_auth(self, request, headers=None, remote_addr=None, remote_user=None,
                    authorization=None, **kwargs):
      ......
            # If remote group sync is enabled, sync the remote groups with local StackStorm roles
            if cfg.CONF.rbac.sync_remote_groups:
                LOG.debug('Retrieving auth backend groups for user "%s"' % (username),
                          extra=extra)
                try:
                    user_groups = self._auth_backend.get_user_groups(username=username)
                except (NotImplementedError, AttributeError):
                    LOG.debug('Configured auth backend doesn\'t expose user group membership '
                              'information, skipping sync...')
                    return token

                if not user_groups:
                    # No groups, return early
                    return token

                extra['username'] = username
                extra['user_groups'] = user_groups

                LOG.debug('Found "%s" groups for user "%s"' % (len(user_groups), username),
                          extra=extra)

                user_db = UserDB(name=username)
                syncer = RBACRemoteGroupToRoleSyncer()

                try:
                    syncer.sync(user_db=user_db, groups=user_groups)
                except Exception as e:
                    # Note: Failed sync is not fatal
                    LOG.exception('Failed to synchronize remote groups for user "%s"' % (username),
                                  extra=extra)
                else:
                    LOG.debug('Successfully synchronized groups for user "%s"' % (username),
                              extra=extra)

                return token
            return token

        LOG.audit('Invalid credentials provided', extra=extra)
        abort_request()

分析:
3.4.1)
剛纔分析經過調用st2-auth-backend-keystone項目的get_user_groups對某個用戶名獲取其所在的組別列表,
樣例爲['admin']
3.4.2)重點分析
syncer.sync(user_db=user_db, groups=user_groups)
進入:
/opt/stackstorm/st2/lib/python2.7/site-packages/st2common/rbac/syncer.py的sync()

class RBACRemoteGroupToRoleSyncer(object):
    """
    Class which writes remote user role assignments based on the user group membership information
    provided by the auth backend and based on the group to role mapping definitions on disk.
    """

    def sync(self, user_db, groups):
        """
        :param user_db: User to sync the assignments for.
        :type user: :class:`UserDB`

        :param groups: A list of remote groups user is a member of.
        :type groups: ``list`` of ``str``

        :return: A list of mappings which have been created.
        :rtype: ``list`` of :class:`UserRoleAssignmentDB`
        """
        groups = list(set(groups))

        extra = {'user_db': user_db, 'groups': groups}
        LOG.info('Synchronizing remote role assignments for user "%s"' % (str(user_db)),
                 extra=extra)

        # 1. Retrieve group to role mappings for the provided groups
        all_mapping_dbs = GroupToRoleMapping.query(group__in=groups)
        enabled_mapping_dbs = [mapping_db for mapping_db in all_mapping_dbs if
                               mapping_db.enabled]
        disabled_mapping_dbs = [mapping_db for mapping_db in all_mapping_dbs if
                                not mapping_db.enabled]

        if not all_mapping_dbs:
            LOG.debug('No group to role mappings found for user "%s"' % (str(user_db)), extra=extra)

        # 2. Remove all the existing remote role assignments
        remote_assignment_dbs = UserRoleAssignment.query(user=user_db.name, is_remote=True)

        existing_role_names = [assignment_db.role for assignment_db in remote_assignment_dbs]
        existing_role_names = set(existing_role_names)
        current_role_names = set([])

        for mapping_db in all_mapping_dbs:
            for role in mapping_db.roles:
                current_role_names.add(role)

        # A list of new role assignments which should be added to the database
        new_role_names = current_role_names.difference(existing_role_names)

        # A list of role assignments which need to be updated in the database
        updated_role_names = existing_role_names.intersection(current_role_names)

        # A list of role assignments which should be removed from the database
        removed_role_names = (existing_role_names - new_role_names)

        # Also remove any assignments for mappings which are disabled in the database
        for mapping_db in disabled_mapping_dbs:
            for role in mapping_db.roles:
                removed_role_names.add(role)

        LOG.debug('New role assignments: %r' % (new_role_names))
        LOG.debug('Updated role assignments: %r' % (updated_role_names))
        LOG.debug('Removed role assignments: %r' % (removed_role_names))

        # Build a list of role assignments to delete
        role_names_to_delete = updated_role_names.union(removed_role_names)
        role_assignment_dbs_to_delete = [role_assignment_db for role_assignment_db
                                         in remote_assignment_dbs
                                         if role_assignment_db.role in role_names_to_delete]

        UserRoleAssignment.query(user=user_db.name, role__in=role_names_to_delete,
                                 is_remote=True).delete()

        # 3. Create role assignments for all the current groups
        created_assignments_dbs = []
        for mapping_db in enabled_mapping_dbs:
            extra['mapping_db'] = mapping_db

            for role_name in mapping_db.roles:
                role_db = rbac_services.get_role_by_name(name=role_name)

                if not role_db:
                    # Gracefully skip assignment for role which doesn't exist in the db
                    LOG.info('Role with name "%s" for mapping "%s" not found, skipping assignment.'
                             % (role_name, str(mapping_db)), extra=extra)
                    continue

                description = ('Automatic role assignment based on the remote user membership in '
                               'group "%s"' % (mapping_db.group))
                assignment_db = rbac_services.assign_role_to_user(role_db=role_db, user_db=user_db,
                                                                  description=description,
                                                                  is_remote=True,
                                                                  source=mapping_db.source)
                assert assignment_db.is_remote is True
                created_assignments_dbs.append(assignment_db)

        LOG.debug('Created %s new remote role assignments for user "%s"' %
                  (len(created_assignments_dbs), str(user_db)), extra=extra)

        return (created_assignments_dbs, role_assignment_dbs_to_delete)

分析:
3.4.1.1)打印變量
(Pdb) p user_db
<UserDB: UserDB(id=None, is_service=False, name="[email protected]", nicknames={})>
(Pdb) p type(user_db)
<class 'st2common.models.db.auth.UserDB'>
(Pdb) p user_db.__dict__
{'_cls': 'UserDB'}
(Pdb) p groups
[u'admin']
(Pdb) p extra
{'user_db': <UserDB: UserDB(id=None, is_service=False, name="[email protected]", nicknames={})>, 'groups': [u'admin']}

(Pdb) p all_mapping_dbs
[<GroupToRoleMappingDB: GroupToRoleMappingDB(description="Automatically grant admin role to all Openstack domain admin group members.", enabled=True, group="admin", id=5d4300594db3f70010259ba5, roles=[u'admin'], source="mappings/dozer_admin.yaml")>]
(Pdb) p type(all_mapping_dbs)
<class 'mongoengine.queryset.queryset.QuerySet'>
(Pdb) p all_mapping_dbs.__dict__
{'_none': False, '_result_cache': [<GroupToRoleMappingDB: GroupToRoleMappingDB(description="Automatically grant admin role to all Openstack domain admin group members.", enabled=True, group="admin", id=5d4300594db3f70010259ba5, roles=[u'admin'], source="mappings/dozer_admin.yaml")>], 'only_fields': [], '_hint': -1, '_batch_size': None, '_iter': False, '_as_pymongo_coerce': False, '_search_text': None, '_read_preference': None, '_snapshot': False, '_cursor_obj': <pymongo.cursor.Cursor object at 0x532c950>, '_slave_okay': False, '_ordering': [], '_class_check': True, '_query_obj': <mongoengine.queryset.visitor.Q object at 0x532c5d0>, '_scalar': [], '_document': <class 'st2common.models.db.rbac.GroupToRoleMappingDB'>, '_where_clause': None, '_mongo_query': {'group': {'$in': [u'admin']}}, '_limit': None, '_has_more': False, '_loaded_fields': <mongoengine.queryset.field_list.QueryFieldList object at 0x532c550>, '_as_pymongo': False, '_initial_query': {}, '_comment': None, '_skip': 0, '_auto_dereference': True, '_timeout': True, '_collection_obj': Collection(Database(MongoClient(host=['mongodb.openstack.svc.cluster.local:27017'], document_class=dict, tz_aware=True, connect=True, ssl=False, read_preference=Primary()), u'st2'), u'group_to_role_mapping_d_b'), '_max_time_ms': None}
(Pdb) p mapping_db.enabled
True

(Pdb) p enabled_mapping_dbs
[<GroupToRoleMappingDB: GroupToRoleMappingDB(description="Automatically grant admin role to all Openstack domain admin group members.", enabled=True, group="admin", id=5d4300594db3f70010259ba5, roles=[u'admin'], source="mappings/dozer_admin.yaml")>]
(Pdb) p type(enabled_mapping_dbs)
<type 'list'>
(Pdb) p len(enabled_mapping_dbs)
1

(Pdb) p enabled_mapping_dbs[0]
<GroupToRoleMappingDB: GroupToRoleMappingDB(description="Automatically grant admin role to all Openstack domain admin group members.", enabled=True, group="admin", id=5d4300594db3f70010259ba5, roles=[u'admin'], source="mappings/dozer_admin.yaml")>
(Pdb) p type(enabled_mapping_dbs[0])
<class 'st2common.models.db.rbac.GroupToRoleMappingDB'>
(Pdb) p enabled_mapping_dbs[0].__dict__
{'_cls': 'GroupToRoleMappingDB'}
(Pdb) p disabled_mapping_dbs
[]

(Pdb) p remote_assignment_dbs
[<UserRoleAssignmentDB: UserRoleAssignmentDB(description="Automatic role assignment based on the remote user membership in group "admin"", id=5d490ecfa0b71a0031703b6c, is_remote=True, role="admin", source="mappings/dozer_admin.yaml", user="[email protected]")>]
(Pdb) p type(remote_assignment_dbs)
<class 'mongoengine.queryset.queryset.QuerySet'>
(Pdb) p remote_assignment_dbs.__dict__
{'_none': False, '_result_cache': [<UserRoleAssignmentDB: UserRoleAssignmentDB(description="Automatic role assignment based on the remote user membership in group "admin"", id=5d490ecfa0b71a0031703b6c, is_remote=True, role="admin", source="mappings/dozer_admin.yaml", user="[email protected]")>], 'only_fields': [], '_hint': -1, '_batch_size': None, '_iter': False, '_as_pymongo_coerce': False, '_search_text': None, '_read_preference': None, '_snapshot': False, '_cursor_obj': <pymongo.cursor.Cursor object at 0x5566390>, '_slave_okay': False, '_ordering': [], '_class_check': True, '_query_obj': <mongoengine.queryset.visitor.Q object at 0x55669d0>, '_scalar': [], '_document': <class 'st2common.models.db.rbac.UserRoleAssignmentDB'>, '_where_clause': None, '_mongo_query': {'is_remote': True, 'user': u'[email protected]'}, '_limit': None, '_has_more': False, '_loaded_fields': <mongoengine.queryset.field_list.QueryFieldList object at 0x5566310>, '_as_pymongo': False, '_initial_query': {}, '_comment': None, '_skip': 0, '_auto_dereference': True, '_timeout': True, '_collection_obj': Collection(Database(MongoClient(host=['mongodb.openstack.svc.cluster.local:27017'], document_class=dict, tz_aware=True, connect=True, ssl=False, read_preference=Primary()), u'st2'), u'user_role_assignment_d_b'), '_max_time_ms': None}

(Pdb) p existing_role_names
[u'admin']
(Pdb) p existing_role_names
set([u'admin'])

(Pdb) p mapping_db.roles
[u'admin']
(Pdb) p current_role_names
set([u'admin'])
(Pdb) p existing_role_names
set([u'admin'])
(Pdb) p new_role_names
set([])

intersection: 交集
(Pdb) p updated_role_names
set([u'admin'])
(Pdb) p removed_role_names
set([u'admin'])
(Pdb) p existing_role_names
set([u'admin'])
(Pdb) p new_role_names
set([])

(Pdb) p role_names_to_delete
set([u'admin'])
(Pdb) p role_assignment_dbs_to_delete
[<UserRoleAssignmentDB: UserRoleAssignmentDB(description="Automatic role assignment based on the remote user membership in group "admin"", id=5d490ecfa0b71a0031703b6c, is_remote=True, role="admin", source="mappings/dozer_admin.yaml", user="[email protected]")>]
(Pdb) p type(role_assignment_dbs_to_delete)
<type 'list'>
(Pdb) p len(role_assignment_dbs_to_delete)
1
(Pdb) p role_assignment_dbs_to_delete[0]
<UserRoleAssignmentDB: UserRoleAssignmentDB(description="Automatic role assignment based on the remote user membership in group "admin"", id=5d490ecfa0b71a0031703b6c, is_remote=True, role="admin", source="mappings/dozer_admin.yaml", user="[email protected]")>
(Pdb) p type(role_assignment_dbs_to_delete[0])
<class 'st2common.models.db.rbac.UserRoleAssignmentDB'>

(Pdb) p user_db.name
u'[email protected]'
(Pdb) p role_names_to_delete
set([u'admin'])

(Pdb) p enabled_mapping_dbs
[<GroupToRoleMappingDB: GroupToRoleMappingDB(description="Automatically grant admin role to all Openstack domain admin group members.", enabled=True, group="admin", id=5d4300594db3f70010259ba5, roles=[u'admin'], source="mappings/dozer_admin.yaml")>]
(Pdb) p type(enabled_mapping_dbs)
<type 'list'>
(Pdb) p len(enabled_mapping_dbs)
1

(Pdb) p role_db
<RoleDB: RoleDB(description="admin", id=5d42fffbb5cf730001e4e761, name="admin", permission_grants=[], system=True)>
(Pdb) p type(role_db)
<class 'st2common.models.db.rbac.RoleDB'>
(Pdb) p role_db.__dict__
{'_cls': 'RoleDB'}

(Pdb) p mapping_db.group
u'admin'
(Pdb) p description
u'Automatic role assignment based on the remote user membership in group "admin"'

(Pdb) p created_assignments_dbs
[<UserRoleAssignmentDB: UserRoleAssignmentDB(description="Automatic role assignment based on the remote user membership in group "admin"", id=5d4a34bec8a3df009bf425a5, is_remote=True, role="admin", source="mappings/dozer_admin.yaml", user="[email protected]")>]
(Pdb) p type(created_assignments_dbs)
<type 'list'>
(Pdb) p role_assignment_dbs_to_delete
[<UserRoleAssignmentDB: UserRoleAssignmentDB(description="Automatic role assignment based on the remote user membership in group "admin"", id=5d490ecfa0b71a0031703b6c, is_remote=True, role="admin", source="mappings/dozer_admin.yaml", user="[email protected]")>]
(Pdb) p type(role_assignment_dbs_to_delete)
<type 'list'>

第二次調試,用[email protected]用戶,對應
domain角色爲admin, project角色爲普通用戶
輸入參數:
(Pdb) p user_db
<UserDB: UserDB(id=None, is_service=False, name="[email protected]", nicknames={})>
(Pdb) p user_groups
[u'_member_']

(Pdb) p all_mapping_dbs
[]
(Pdb) p enabled_mapping_dbs
[]
(Pdb) p disabled_mapping_dbs
[]

(Pdb) p remote_assignment_dbs
[]
(Pdb) p new_role_names
set([])

(Pdb) p removed_role_names
set([])

(Pdb) p role_names_to_delete
set([])

(Pdb) p role_assignment_dbs_to_delete
[]


4 總結
st2auth服務是校驗服務,校驗的主要流程是:
4.1 根據配置的校驗後端,例如st2-auth-backend-keystone,
調用校驗後端的校驗方法,這裏向keystone發送校驗,根據不同的校驗模式,有用戶名和密碼模式,
郵箱模式,token模式,分別向keystone發送不同的校驗參數,發送請求爲
'http://keystone-api.openstack.svc.cluster.local:80/v3/auth/tokens'
4.2 如果校驗成功,此時查詢當前用戶名所在的rbac所在組列表,
這裏仍然是調用校驗後端的get_user_groups方法,具體是向keystone發送
形如: u'http://keystone-api.openstack.svc.cluster.local:80/v3/role_assignments?user.id=6ccfd42a4ac64394836273eb6a7af1c9'的url得到role列表,對每個rule遍歷其
role_assignments,獲取role_assignment["role"]["name"]作爲角色組。
得到所在組別後,同步用戶到組列表中,具體是查詢組列表對應的映射對象列表,
遍歷映射對象列表中每個映射的roles,對每個role,賦予給對應用戶。

參考:
stackstorm 2.6代碼
st2-auth-backend-keystone代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章