Add Keystone authentication to rest API
Authenticate requests via keystone to the REST API. Change-Id: Ie45e5325c2f9cd892c60ff37acfcd1ceecc82e32
This commit is contained in:
parent
f00ab5ae7d
commit
62bfbf6045
161
magnum/api/auth.py
Normal file
161
magnum/api/auth.py
Normal file
@ -0,0 +1,161 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
#
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import re
|
||||
|
||||
from keystoneclient.middleware import auth_token
|
||||
from oslo.config import cfg
|
||||
from oslo.utils import importutils
|
||||
from pecan import hooks
|
||||
|
||||
from magnum.common import context
|
||||
from magnum.openstack.common._i18n import _
|
||||
from magnum.openstack.common import log as logging
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
OPT_GROUP_NAME = 'keystone_authtoken'
|
||||
|
||||
AUTH_OPTS = [
|
||||
cfg.BoolOpt('enable_authentication',
|
||||
default=True,
|
||||
help='This option enables or disables user authentication '
|
||||
'via keystone. Default value is True.'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(AUTH_OPTS)
|
||||
CONF.register_opts(auth_token.opts, group=OPT_GROUP_NAME)
|
||||
|
||||
PUBLIC_ENDPOINTS = [
|
||||
'^/?$',
|
||||
'^/v[0-9]+/?$',
|
||||
'^/v[0-9]+/triggers',
|
||||
'^/camp/platform_endpoints',
|
||||
'^/camp/camp_v1_1_endpoint'
|
||||
]
|
||||
|
||||
|
||||
def install(app, conf):
|
||||
if conf.get('enable_authentication'):
|
||||
return AuthProtocolWrapper(app, conf=dict(conf.get(OPT_GROUP_NAME)))
|
||||
else:
|
||||
LOG.warning(_('Keystone authentication is disabled by Magnum '
|
||||
'configuration parameter enable_authentication. '
|
||||
'Magnum will not authenticate incoming request. '
|
||||
'In order to enable authentication set '
|
||||
'enable_authentication option to True.'))
|
||||
|
||||
return app
|
||||
|
||||
|
||||
class AuthHelper(object):
|
||||
"""Helper methods for Auth."""
|
||||
|
||||
def __init__(self):
|
||||
endpoints_pattern = '|'.join(pe for pe in PUBLIC_ENDPOINTS)
|
||||
self._public_endpoints_regexp = re.compile(endpoints_pattern)
|
||||
|
||||
def is_endpoint_public(self, path):
|
||||
return self._public_endpoints_regexp.match(path)
|
||||
|
||||
|
||||
class AuthProtocolWrapper(auth_token.AuthProtocol):
|
||||
"""A wrapper on Keystone auth_token AuthProtocol.
|
||||
|
||||
Does not perform verification of authentication tokens for pub routes in
|
||||
the API. Public routes are those defined by PUBLIC_ENDPOINTS
|
||||
|
||||
"""
|
||||
|
||||
def __call__(self, env, start_response):
|
||||
path = env.get('PATH_INFO')
|
||||
if AUTH.is_endpoint_public(path):
|
||||
return self.app(env, start_response)
|
||||
return super(AuthProtocolWrapper, self).__call__(env, start_response)
|
||||
|
||||
|
||||
class AuthInformationHook(hooks.PecanHook):
|
||||
|
||||
def before(self, state):
|
||||
if not CONF.get('enable_authentication'):
|
||||
return
|
||||
# Skip authentication for public endpoints
|
||||
if AUTH.is_endpoint_public(state.request.path):
|
||||
return
|
||||
|
||||
headers = state.request.headers
|
||||
user_id = headers.get('X-User-Id')
|
||||
if user_id is None:
|
||||
LOG.debug("X-User-Id header was not found in the request")
|
||||
raise Exception('Not authorized')
|
||||
|
||||
roles = self._get_roles(state.request)
|
||||
|
||||
project_id = headers.get('X-Project-Id')
|
||||
user_name = headers.get('X-User-Name', '')
|
||||
|
||||
domain = headers.get('X-Domain-Name')
|
||||
project_domain_id = headers.get('X-Project-Domain-Id', '')
|
||||
user_domain_id = headers.get('X-User-Domain-Id', '')
|
||||
|
||||
# Get the auth token
|
||||
try:
|
||||
recv_auth_token = headers.get('X-Auth-Token',
|
||||
headers.get(
|
||||
'X-Storage-Token'))
|
||||
except ValueError:
|
||||
LOG.debug("No auth token found in the request.")
|
||||
raise Exception('Not authorized')
|
||||
auth_url = headers.get('X-Auth-Url')
|
||||
if auth_url is None:
|
||||
importutils.import_module('keystoneclient.middleware.auth_token')
|
||||
auth_url = cfg.CONF.keystone_authtoken.auth_uri
|
||||
|
||||
auth_token_info = state.request.environ.get('keystone.token_info')
|
||||
identity_status = headers.get('X-Identity-Status')
|
||||
if identity_status == 'Confirmed':
|
||||
ctx = context.RequestContext(auth_token=recv_auth_token,
|
||||
auth_token_info=auth_token_info,
|
||||
user=user_id,
|
||||
tenant=project_id,
|
||||
domain=domain,
|
||||
user_domain=user_domain_id,
|
||||
project_domain=project_domain_id,
|
||||
user_name=user_name,
|
||||
roles=roles,
|
||||
auth_url=auth_url)
|
||||
state.request.security_context = ctx
|
||||
else:
|
||||
LOG.debug("The provided identity is not confirmed.")
|
||||
raise Exception('Not authorized. Identity not confirmed.')
|
||||
return
|
||||
|
||||
def _get_roles(self, req):
|
||||
"""Get the list of roles."""
|
||||
|
||||
if 'X-Roles' in req.headers:
|
||||
roles = req.headers.get('X-Roles', '')
|
||||
else:
|
||||
# Fallback to deprecated role header:
|
||||
roles = req.headers.get('X-Role', '')
|
||||
if roles:
|
||||
LOG.warn(_("X-Roles is missing. Using deprecated X-Role "
|
||||
"header"))
|
||||
return [r.strip() for r in roles.split(',')]
|
||||
|
||||
|
||||
AUTH = AuthHelper()
|
@ -14,11 +14,14 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from magnum.api import auth
|
||||
|
||||
# Pecan Application Configurations
|
||||
app = {
|
||||
'root': 'magnum.api.controllers.root.RootController',
|
||||
'modules': ['magnum.api'],
|
||||
'debug': False
|
||||
'debug': False,
|
||||
'hooks': [auth.AuthInformationHook()]
|
||||
}
|
||||
|
||||
# Custom Configurations must be in Python dictionary format::
|
||||
|
54
magnum/common/context.py
Normal file
54
magnum/common/context.py
Normal file
@ -0,0 +1,54 @@
|
||||
# Copyright 2014 - Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import inspect
|
||||
|
||||
from magnum.openstack.common import context
|
||||
|
||||
|
||||
class RequestContext(context.RequestContext):
|
||||
def __init__(self, auth_token=None, user=None, tenant=None, domain=None,
|
||||
user_domain=None, project_domain=None, is_admin=False,
|
||||
read_only=False, request_id=None, user_name=None, roles=None,
|
||||
auth_url=None, trust_id=None, auth_token_info=None):
|
||||
super(RequestContext, self).__init__(auth_token=auth_token,
|
||||
user=user, tenant=tenant,
|
||||
domain=domain,
|
||||
user_domain=user_domain,
|
||||
project_domain=project_domain,
|
||||
is_admin=is_admin,
|
||||
read_only=read_only,
|
||||
show_deleted=False,
|
||||
request_id=request_id)
|
||||
self.roles = roles
|
||||
self.user_name = user_name
|
||||
self.auth_url = auth_url
|
||||
self.trust_id = trust_id
|
||||
self.auth_token_info = auth_token_info
|
||||
|
||||
def to_dict(self):
|
||||
data = super(RequestContext, self).to_dict()
|
||||
data.update(roles=self.roles, user_name=self.user_name,
|
||||
auth_url=self.auth_url,
|
||||
auth_token_info=self.auth_token_info,
|
||||
trust_id=self.trust_id)
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, values):
|
||||
allowed = [arg for arg in
|
||||
inspect.getargspec(RequestContext.__init__).args
|
||||
if arg != 'self']
|
||||
kwargs = dict((k, v) for (k, v) in values.items() if k in allowed)
|
||||
return cls(**kwargs)
|
Loading…
x
Reference in New Issue
Block a user