df9181e564
This creates a noauth2 auth_strategy which is similar to noauth, except it only gives you an admin context if the username passed in is 'admin'. This allows testing of non admin activities. noauth is deprecated as of this commit. While we expect that it would only be used in testing, it is exposed as a conf option, so could be used behind a different auth proxy. Also make the error path for pipeline loading contain a full LOG.exception. This is a fatal condition for nova, and the current error was often quite opaque. The full stack trace during this fatal error makes addressing paste.ini issues much more straight forward. DocImpact Change-Id: I7cb5ab3e43a1e3bd7ccba0480053361743f859b2
171 lines
6.1 KiB
Python
171 lines
6.1 KiB
Python
# Copyright (c) 2011 OpenStack Foundation
|
|
#
|
|
# 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.
|
|
"""
|
|
Common Auth Middleware.
|
|
|
|
"""
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from oslo_middleware import request_id
|
|
from oslo_serialization import jsonutils
|
|
import webob.dec
|
|
import webob.exc
|
|
|
|
from nova import context
|
|
from nova.i18n import _
|
|
from nova.openstack.common import versionutils
|
|
from nova import wsgi
|
|
|
|
|
|
auth_opts = [
|
|
cfg.BoolOpt('api_rate_limit',
|
|
default=False,
|
|
help='Whether to use per-user rate limiting for the api. '
|
|
'This option is only used by v2 api. Rate limiting '
|
|
'is removed from v3 api.'),
|
|
cfg.StrOpt('auth_strategy',
|
|
default='keystone',
|
|
help='''
|
|
The strategy to use for auth: keystone, noauth (deprecated), or
|
|
noauth2. Both noauth and noauth2 are designed for testing only, as
|
|
they do no actual credential checking. noauth provides administrative
|
|
credentials regardless of the passed in user, noauth2 only does if
|
|
'admin' is specified as the username.
|
|
'''),
|
|
cfg.BoolOpt('use_forwarded_for',
|
|
default=False,
|
|
help='Treat X-Forwarded-For as the canonical remote address. '
|
|
'Only enable this if you have a sanitizing proxy.'),
|
|
]
|
|
|
|
CONF = cfg.CONF
|
|
CONF.register_opts(auth_opts)
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def _load_pipeline(loader, pipeline):
|
|
filters = [loader.get_filter(n) for n in pipeline[:-1]]
|
|
app = loader.get_app(pipeline[-1])
|
|
filters.reverse()
|
|
for filter in filters:
|
|
app = filter(app)
|
|
return app
|
|
|
|
|
|
def pipeline_factory(loader, global_conf, **local_conf):
|
|
"""A paste pipeline replica that keys off of auth_strategy."""
|
|
# TODO(sdague): remove deprecated noauth in Liberty
|
|
if CONF.auth_strategy == 'noauth':
|
|
versionutils.report_deprecated_feature(
|
|
LOG,
|
|
('The noauth middleware will be removed in Liberty.'
|
|
' noauth2 should be used instead.'))
|
|
pipeline = local_conf[CONF.auth_strategy]
|
|
if not CONF.api_rate_limit:
|
|
limit_name = CONF.auth_strategy + '_nolimit'
|
|
pipeline = local_conf.get(limit_name, pipeline)
|
|
pipeline = pipeline.split()
|
|
return _load_pipeline(loader, pipeline)
|
|
|
|
|
|
def pipeline_factory_v21(loader, global_conf, **local_conf):
|
|
"""A paste pipeline replica that keys off of auth_strategy."""
|
|
return _load_pipeline(loader, local_conf[CONF.auth_strategy].split())
|
|
|
|
|
|
# NOTE(oomichi): This pipeline_factory_v3 is for passing check-grenade-dsvm.
|
|
pipeline_factory_v3 = pipeline_factory_v21
|
|
|
|
|
|
class InjectContext(wsgi.Middleware):
|
|
"""Add a 'nova.context' to WSGI environ."""
|
|
|
|
def __init__(self, context, *args, **kwargs):
|
|
self.context = context
|
|
super(InjectContext, self).__init__(*args, **kwargs)
|
|
|
|
@webob.dec.wsgify(RequestClass=wsgi.Request)
|
|
def __call__(self, req):
|
|
req.environ['nova.context'] = self.context
|
|
return self.application
|
|
|
|
|
|
class NovaKeystoneContext(wsgi.Middleware):
|
|
"""Make a request context from keystone headers."""
|
|
|
|
@webob.dec.wsgify(RequestClass=wsgi.Request)
|
|
def __call__(self, req):
|
|
user_id = req.headers.get('X_USER')
|
|
user_id = req.headers.get('X_USER_ID', user_id)
|
|
if user_id is None:
|
|
LOG.debug("Neither X_USER_ID nor X_USER found in request")
|
|
return webob.exc.HTTPUnauthorized()
|
|
|
|
roles = self._get_roles(req)
|
|
|
|
if 'X_TENANT_ID' in req.headers:
|
|
# This is the new header since Keystone went to ID/Name
|
|
project_id = req.headers['X_TENANT_ID']
|
|
else:
|
|
# This is for legacy compatibility
|
|
project_id = req.headers['X_TENANT']
|
|
project_name = req.headers.get('X_TENANT_NAME')
|
|
user_name = req.headers.get('X_USER_NAME')
|
|
|
|
req_id = req.environ.get(request_id.ENV_REQUEST_ID)
|
|
|
|
# Get the auth token
|
|
auth_token = req.headers.get('X_AUTH_TOKEN',
|
|
req.headers.get('X_STORAGE_TOKEN'))
|
|
|
|
# Build a context, including the auth_token...
|
|
remote_address = req.remote_addr
|
|
if CONF.use_forwarded_for:
|
|
remote_address = req.headers.get('X-Forwarded-For', remote_address)
|
|
|
|
service_catalog = None
|
|
if req.headers.get('X_SERVICE_CATALOG') is not None:
|
|
try:
|
|
catalog_header = req.headers.get('X_SERVICE_CATALOG')
|
|
service_catalog = jsonutils.loads(catalog_header)
|
|
except ValueError:
|
|
raise webob.exc.HTTPInternalServerError(
|
|
_('Invalid service catalog json.'))
|
|
|
|
# NOTE(jamielennox): This is a full auth plugin set by auth_token
|
|
# middleware in newer versions.
|
|
user_auth_plugin = req.environ.get('keystone.token_auth')
|
|
|
|
ctx = context.RequestContext(user_id,
|
|
project_id,
|
|
user_name=user_name,
|
|
project_name=project_name,
|
|
roles=roles,
|
|
auth_token=auth_token,
|
|
remote_address=remote_address,
|
|
service_catalog=service_catalog,
|
|
request_id=req_id,
|
|
user_auth_plugin=user_auth_plugin)
|
|
|
|
req.environ['nova.context'] = ctx
|
|
return self.application
|
|
|
|
def _get_roles(self, req):
|
|
"""Get the list of roles."""
|
|
|
|
roles = req.headers.get('X_ROLES', '')
|
|
return [r.strip() for r in roles.split(',')]
|