梳理Openstack组件Keystone知识-从0到1
梳理总结
- Keystone通过setuptools打包,pbr作为setuptools插件支撑打包,把静态配置放置于setup.cfg
- Paste Deploy作为WSGI Server和WSGI Application的桥梁
- /usr/bin/keystone-wsgi-admin,keystone-wsgi-public 由pbr自动生成,这在keystone.egg-info/entry_points.txt定义
- entry_points.txt 定义了启动的切入点如下
[wsgi_scripts]
keystone-wsgi-admin = keystone.server.wsgi:initialize_admin_application
keystone-wsgi-public = keystone.server.wsgi:initialize_public_application
- /usr/bin/keystone-wsgi-admin如下:
if __name__ == "__main__":
import argparse
import socket
import wsgiref.simple_server as wss
#省略
...
server.serve_forever()
else:
application = None
app_lock = threading.Lock()
with app_lock:
if application is None:
application = initialize_admin_application()
- /usr/bin/keystone-wsgi-public
application = initialize_public_application()
- 上述两个脚本为供httpd中配置文件调用如下
#vi /etc/httpd/conf.d/wsgi-keystone.conf
Listen 192.168.41.11:5000
Listen 192.168.41.11:35357
<VirtualHost *:5000>
WSGIDaemonProcess keystone-public processes=5 threads=1 user=keystone group=keystone display-name=%{GROUP}
WSGIProcessGroup keystone-public
WSGIScriptAlias / /usr/bin/keystone-wsgi-public
WSGIApplicationGroup %{GLOBAL}
WSGIPassAuthorization On
ErrorLogFormat "%{cu}t %M"
ErrorLog /var/log/httpd/keystone-error.log
CustomLog /var/log/httpd/keystone-access.log combined
<Directory /usr/bin>
Require all granted
</Directory>
</VirtualHost>
<VirtualHost *:35357>
WSGIDaemonProcess keystone-admin processes=5 threads=1 user=keystone group=keystone display-name=%{GROUP}
WSGIProcessGroup keystone-admin
WSGIScriptAlias / /usr/bin/keystone-wsgi-admin
WSGIApplicationGroup %{GLOBAL}
WSGIPassAuthorization On
ErrorLogFormat "%{cu}t %M"
ErrorLog /var/log/httpd/keystone-error.log
CustomLog /var/log/httpd/keystone-access.log combined
<Directory /usr/bin>
Require all granted
</Directory>
</VirtualHost>
- 如果非httpd调用
# vi /usr/lib/systemd/system/openstack-keystone.service
ExecStart=/usr/bin/keystone-all
Keystone的Httpd启动方式解析
- Httpd启动keystone-wsgi-public监听5000,启动keystone-wsgi-admin监听35357
- 脚本中启动WSGI应用通过调用keystone.server.wsgi.py中方法initialize_admin_application
- initialize_admin_application调用initialize_application
- initialize_application中调用keystone.version.service.py中loadadpp
- loadapp方法使用paste deploy加载配置文件keystone-paste.ini启动WSGI应用
- keystone-paste.ini中配置了app的集合composite:main,composite:admin
- paste机制不同的url加载到不同的pipeline,pipeline中有若干filter加最后的application承接服务请求
部分代码详解
wsgi.py
#keystone.server.wsgi.py
#/usr/bin下两个不同脚本分别调用了不同的方法,同时调用了同一个方法传递了不同的参数“admin”,“main”
def initialize_application(name, post_log_configured_function=lambda: None):
common.configure()
# Log the options used when starting if we're in debug mode...
if CONF.debug:
CONF.log_opt_values(logging.getLogger(CONF.prog), logging.DEBUG)
environment.use_stdlib()
post_log_configured_function()
def loadapp():
return keystone_service.loadapp(
'config:%s' % config.find_paste_config(), name)
_unused, application = common.setup_backends(
startup_application_fn=loadapp)
return application
def initialize_admin_application():
return initialize_application('admin')
def initialize_public_application():
return initialize_application('main')
service.py
#keystone.version.service.py
#wsgi.py中的调用参数传递到loadapp中作为loadapp的参数
def loadapp(conf, name):
# NOTE(blk-u): Save the application being loaded in the controllers module.
# This is similar to how public_app_factory() and v3_app_factory()
# register the version with the controllers module.
controllers.latest_app = deploy.loadapp(conf, name=name)
return controllers.latest_app
keystone-paste.ini
#keystone-paste.ini
#loadapp中不同参数促发配置composite
[composite:main]
use = egg:Paste#urlmap
/v2.0 = public_api
/v3 = api_v3
/ = public_version_api
[composite:admin]
use = egg:Paste#urlmap
/v2.0 = admin_api
/v3 = api_v3
/ = admin_version_api
#Paste中composite会分发到pipeline中,经由若干filter处理
[pipeline:public_api]
# The last item in this pipeline must be public_service or an equivalent
# application. It cannot be a filter.
pipeline = cors sizelimit url_normalize request_id admin_token_auth build_auth_context token_auth json_body ec2_extension public_service
[pipeline:admin_api]
# The last item in this pipeline must be admin_service or an equivalent
# application. It cannot be a filter.
pipeline = cors sizelimit url_normalize request_id admin_token_auth build_auth_context token_auth json_body ec2_extension s3_extension admin_service
[pipeline:api_v3]
# The last item in this pipeline must be service_v3 or an equivalent
# application. It cannot be a filter.
pipeline = cors sizelimit url_normalize request_id admin_token_auth build_auth_context token_auth json_body ec2_extension_v3 s3_extension service_v3
#多个pipeline都包含过滤器 admin_token_auth,使用处理器为egg:keysonte#admin_token_auth,该信息可以在setuptools的相关文件entry_points.txt中找到
[filter:admin_token_auth]
# This is deprecated in the M release and will be removed in the O release.
# Use `keystone-manage bootstrap` and remove this from the pipelines below.
use = egg:keystone#admin_token_auth
entry_points.txt
[paste.filter_factory]
admin_token_auth = keystone.middleware:AdminTokenAuthMiddleware.factory
core.py
#keystone.middleware/core.py
#获取Http请求头中的X-Auth-Token值
# Header used to transmit the auth token
AUTH_TOKEN_HEADER = 'X-Auth-Token'
# Header used to transmit the subject token
SUBJECT_TOKEN_HEADER = 'X-Subject-Token'
class AdminTokenAuthMiddleware(wsgi.Middleware):
"""A trivial filter that checks for a pre-defined admin token.
Sets 'is_admin' to true in the context, expected to be checked by
methods that are admin-only.
"""
def __init__(self, application):
super(AdminTokenAuthMiddleware, self).__init__(application)
LOG.warning(_LW("The admin_token_auth middleware presents a security "
"risk and should be removed from the "
"[pipeline:api_v3], [pipeline:admin_api], and "
"[pipeline:public_api] sections of your paste ini "
"file."))
def process_request(self, request):
token = request.headers.get(AUTH_TOKEN_HEADER)
context = request.environ.get(CONTEXT_ENV, {})
context['is_admin'] = CONF.admin_token and (token == CONF.admin_token)
request.environ[CONTEXT_ENV] = context
除了admin_token_auth还有token_auth同样取X-Auth-Token值和X-Subject-Token
#取请求头,存入是上下文token_id中
class TokenAuthMiddleware(wsgi.Middleware):
def process_request(self, request):
token = request.headers.get(AUTH_TOKEN_HEADER)
context = request.environ.get(CONTEXT_ENV, {})
context['token_id'] = token
if SUBJECT_TOKEN_HEADER in request.headers:
context['subject_token_id'] = request.headers[SUBJECT_TOKEN_HEADER]
request.environ[CONTEXT_ENV] = context
#pipeline的最后一个为application,导向app相关配置
[app:public_service]
use = egg:keystone#public_service
[app:service_v3]
use = egg:keystone#service_v3
[app:admin_service]
use = egg:keystone#admin_service
Paste中app的指向可以在entry_points.txt找到
#entry_points.txt中应用的配置
[paste.app_factory]
admin_service = keystone.version.service:admin_app_factory
admin_version_service = keystone.version.service:admin_version_app_factory
public_service = keystone.version.service:public_app_factory
public_version_service = keystone.version.service:public_version_app_factory
service_v3 = keystone.version.service:v3_app_factory
keystone.version.service.py
#应用分发路由
def v3_app_factory(global_conf, **local_conf):
controllers.register_version('v3')
mapper = routes.Mapper()
sub_routers = []
_routers = []
# NOTE(dstanek): Routers should be ordered by their frequency of use in
# a live system. This is due to the routes implementation. The most
# frequently used routers should appear first.
all_api_routers = [auth_routers,
assignment_routers,
catalog_routers,
credential_routers,
identity_routers,
policy_routers,
resource_routers,
revoke_routers,
federation_routers,
oauth1_routers,
# TODO(morganfainberg): Remove the simple_cert router
# when PKI and PKIZ tokens are removed.
simple_cert_ext]
if CONF.trust.enabled:
all_api_routers.append(trust_routers)
if CONF.endpoint_policy.enabled:
all_api_routers.append(endpoint_policy_routers)
for api_routers in all_api_routers:
routers_instance = api_routers.Routers()
_routers.append(routers_instance)
routers_instance.append_v3_routers(mapper, sub_routers)
# Add in the v3 version api
sub_routers.append(routers.VersionV3('public', _routers))
return wsgi.ComposingRouter(mapper, sub_routers)
auth_routers具体实现
#路由路径添加
class Routers(wsgi.RoutersBase):
def append_v3_routers(self, mapper, routers):
auth_controller = controllers.Auth()
self._add_resource(
mapper, auth_controller,
path='/auth/tokens',
get_action='validate_token',
head_action='check_token',
post_action='authenticate_for_token',
delete_action='revoke_token',
rel=json_home.build_v3_resource_relation('auth_tokens'))
......
auth/controllers.py中业务处理
#业务的处理实现
class Auth(controller.V3Controller):
......
def authenticate_for_token(self, context, auth=None):
"""Authenticate user and issue a token."""
include_catalog = 'nocatalog' not in context['query_string']
try:
auth_info = AuthInfo.create(context, auth=auth)
auth_context = AuthContext(extras={},
method_names=[],
bind={})
self.authenticate(context, auth_info, auth_context)
if auth_context.get('access_token_id'):
auth_info.set_scope(None, auth_context['project_id'], None)
self._check_and_set_default_scoping(auth_info, auth_context)
(domain_id, project_id, trust, unscoped) = auth_info.get_scope()
method_names = auth_info.get_method_names()
method_names += auth_context.get('method_names', [])
# make sure the list is unique
method_names = list(set(method_names))
expires_at = auth_context.get('expires_at')
# NOTE(morganfainberg): define this here so it is clear what the
# argument is during the issue_v3_token provider call.
metadata_ref = None
token_audit_id = auth_context.get('audit_id')
(token_id, token_data) = self.token_provider_api.issue_v3_token(
auth_context['user_id'], method_names, expires_at, project_id,
domain_id, auth_context, trust, metadata_ref, include_catalog,
parent_audit_id=token_audit_id)
# NOTE(wanghong): We consume a trust use only when we are using
# trusts and have successfully issued a token.
if trust:
self.trust_api.consume_use(trust['id'])
return render_token_data_response(token_id, token_data,
created=True)
except exception.TrustNotFound as e:
raise exception.Unauthorized(e)
.....
#在authenticate_for_token最后调用此方法将令牌放入响应体的X-Subject-Token
def render_token_data_response(token_id, token_data, created=False):
"""Render token data HTTP response.
Stash token ID into the X-Subject-Token header.
"""
headers = [('X-Subject-Token', token_id)]
if created:
status = (201, 'Created')
else:
status = (200, 'OK')
return wsgi.render_response(body=token_data,
status=status, headers=headers)