Keystone auth support

BUT you can disable authentication via config `enable_authentication`

Change-Id: I16c5fbed6f8f0743f77ce59b229cfe76353c88be
This commit is contained in:
lawrancejing 2015-11-10 08:26:30 +00:00
parent 170ca660bd
commit 1608d62535
11 changed files with 203 additions and 2 deletions

View File

@ -12,8 +12,13 @@
import pecan
from oslo_config import cfg
from evoque.api import auth
from evoque.api import config as api_config
CONF = cfg.CONF
def get_pecan_config():
# Set up the pecan configuration
@ -33,7 +38,10 @@ def setup_app(config=None):
**app_conf
)
# TODO(liuqing): Add oslo.middleware cors and keystone auth
# TODO(liuqing): Add oslo.middleware cors
# http://docs.openstack.org/developer/oslo.middleware/cors.html
# Keystone auth middleware
app = auth.install(app, CONF, config.app.acl_public_routes)
return app

35
evoque/api/auth.py Normal file
View File

@ -0,0 +1,35 @@
# 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.
"""Access Control Lists (ACL's) control access the API server."""
from oslo_config import cfg
from evoque.api.middleware import auth_token
CONF = cfg.CONF
def install(app, conf, public_routes):
"""Install ACL check on application.
:param app: A WSGI application.
:param conf: Settings. Dict'ified and passed to keystone middleware
:param public_routes: The list of the routes which will be allowed to
access without authentication.
:return: The same WSGI application with ACL installed.
"""
if not cfg.CONF.api.get('enable_authentication'):
return app
return auth_token.AuthTokenMiddleware(app,
conf=dict(conf),
public_api_routes=public_routes)

View File

@ -21,6 +21,9 @@ app = {
hooks.ContextHook(),
hooks.RPCHook(),
],
'acl_public_routes': [
'/'
],
}
# Custom Configurations must be in Python dictionary format::

View File

@ -12,9 +12,15 @@
from pecan import hooks
from oslo_config import cfg
from evoque.common import context
from evoque.engine.ticket import api as ticket_api
CONF = cfg.CONF
CONF.import_opt('auth_uri', 'keystonemiddleware.auth_token',
group='keystone_authtoken')
class ContextHook(hooks.PecanHook):
"""Configures a request context and attaches it to the request.
@ -52,8 +58,11 @@ class ContextHook(hooks.PecanHook):
roles = headers.get('X-Roles', '').split(',')
auth_token_info = state.request.environ.get('keystone.token_info')
auth_url = CONF.keystone_authtoken.auth_uri
state.request.context = context.make_context(
auth_token=auth_token,
auth_url=auth_url,
auth_token_info=auth_token_info,
user_name=user_name,
user_id=user_id,

View File

View File

@ -0,0 +1,60 @@
# 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 keystonemiddleware import auth_token
from oslo_log import log
from evoque.common import exceptions
from evoque.common.i18n import _
from evoque.common import utils
LOG = log.getLogger(__name__)
class AuthTokenMiddleware(auth_token.AuthProtocol):
"""A wrapper on Keystone auth_token middleware.
Does not perform verification of authentication tokens
for public routes in the API.
"""
def __init__(self, app, conf, public_api_routes=None):
if public_api_routes is None:
public_api_routes = []
route_pattern_tpl = '%s\.json?$'
try:
self.public_api_routes = [re.compile(route_pattern_tpl % route_tpl)
for route_tpl in public_api_routes]
except re.error as e:
msg = _('Cannot compile public API routes: %s') % e
LOG.error(msg)
raise exceptions.ConfigInvalid(error_msg=msg)
super(AuthTokenMiddleware, self).__init__(app, conf)
def __call__(self, env, start_response):
path = utils.safe_rstrip(env.get('PATH_INFO'), '/')
# The information whether the API call is being performed against the
# public API is required for some other components. Saving it to the
# WSGI environment is reasonable thereby.
env['is_public_api'] = any(map(lambda pattern: re.match(pattern, path),
self.public_api_routes))
if env['is_public_api']:
return self._app(env, start_response)
return super(AuthTokenMiddleware, self).__call__(env, start_response)

View File

@ -11,7 +11,7 @@
# under the License.
"""
Glance Management Utility
Evoque Management Utility
"""
import os

View File

@ -10,6 +10,47 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log as logging
from evoque.common.i18n import _
from evoque.common.i18n import _LE
LOG = logging.getLogger(__name__)
class NotImplementedError(NotImplementedError):
pass
class EvoqueException(Exception):
"""Base Evoque Exception
To correctly use this class, inherit from it and define
a 'message' property. That message will get printf'd
with the keyword arguments provided to the constructor.
"""
message = _("An unknown exception occurred.")
code = 500
def __init__(self, message=None, **kwargs):
self.kwargs = kwargs
if 'code' not in self.kwargs and hasattr(self, 'code'):
self.kwargs['code'] = self.code
if message:
self.message = message
try:
self.message = self.message % kwargs
except Exception as e:
# kwargs doesn't match a variable in the message
# log the issue and the kwargs
LOG.exception(_LE('Exception in string format operation, '
'kwargs: %s') % kwargs)
raise e
super(EvoqueException, self).__init__(self.message)
class ConfigInvalid(EvoqueException):
message = _("Invalid configuration file. %(error_msg)s")

36
evoque/common/utils.py Normal file
View File

@ -0,0 +1,36 @@
# 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.
"""Utilities and helper functions."""
import six
from oslo_log import log as logging
from evoque.common.i18n import _LW
LOG = logging.getLogger(__name__)
def safe_rstrip(value, chars=None):
"""Removes trailing characters from a string if that does not make it empty
:param value: A string value that will be stripped.
:param chars: Characters to remove.
:return: Stripped value.
"""
if not isinstance(value, six.string_types):
LOG.warn(_LW("Failed to remove trailing character. Returning original "
"object. Supplied object is not a string: %s,"), value)
return value
return value.rstrip(chars) or value

View File

@ -42,6 +42,11 @@ def list_opts():
default=1000,
help=_('The maximum number of items returned in a '
'single response from a collection resource')),
cfg.BoolOpt('enable_authentication',
default=True,
help=_('This option enables or disables user '
'authentication via Keystone. '
'Default value is True.')),
)),
("DEFAULT", (
cfg.StrOpt('host',

View File

@ -3,12 +3,16 @@
# process, which may cause wedges in the gate later.
pbr>=1.6
keystonemiddleware!=2.4.0,>=2.0.0
oslo.config>=2.6.0 # Apache-2.0
oslo.context>=0.2.0 # Apache-2.0
oslo.db>=3.0.0 # Apache-2.0
oslo.i18n>=1.5.0 # Apache-2.0
oslo.log>=1.8.0 # Apache-2.0
oslo.messaging!=1.17.0,!=1.17.1,!=2.6.0,!=2.6.1,>=1.16.0 # Apache-2.0
oslo.serialization>=1.10.0 # Apache-2.0
oslo.service>=0.10.0 # Apache-2.0
oslo.utils!=2.6.0,>=2.4.0 # Apache-2.0
pecan>=1.0.0
SQLAlchemy<1.1.0,>=0.9.9
sqlalchemy-migrate>=0.9.6