stackstorm 26. 源码分析之----stackstorm的auth服务分析

本文详细分析了StackStorm的auth服务,特别是`st2auth`模块的工作原理。首先,它通过URL `http://st2auth:9100/tokens`接收认证请求,并在`RootController`和`TokenController`中处理。接着,它依据配置文件中的`auth.mode`(此处为`standalone`)选择相应的认证处理器,如`StandaloneAuthHandler`。在`StandaloneAuthHandler`中,它使用`KeystoneAuthenticationBackend`与Keystone服务进行交互,验证用户名和密码。如果验证成功,将同步远程用户组到StackStorm的角色。文章最后总结了st2auth服务的认证流程,包括与Keystone的交互及RBAC角色同步。
摘要由CSDN通过智能技术生成

代码在/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
'ma_a_a@example.org:mc123456'
(Pdb) p split
['ma_a_a@example.org', 'mc123456']
(Pdb) p username
'ma_a_a@example.org'
(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': 'ma_a_a@example.org'}}, '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": &

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值