代码在/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": &