49d83e356a
x-openstack-request-id is the common header name for request ID going forward. Nova, cinder, and neutron return this header. Using the request_id middleware is a convenient way to ensure this header is in the response. We will also be using the middleware to generate the request ID. The ID will be generated by the middleware and is inserted into the request environment. The current code generates the request ID in the constructor of RequestContext; with this change, the ID is generated beforehand and passed in to the RequestContext constructor. Not much different here: a request ID is generated and is available for use in the handling of the request. On the response end, the middleware is again used, this time to attach the x-openstack-request-id header, using the value of the generated request ID. For v3, we will be using the request_id middleware provided in oslo. For v2, nova-specific middleware (compute_request_id.py) is used. Therefore, v3 responses will have the header x-openstack-request-id, and v2 responses will have the header x-compute-request-id (no change). It is necessary to move the existing code out into middleware, so that the old header is not attached to v3 responses. UpgradeImpact: api-paste.ini is modified DocImpact: v3 responses of the API will only include x-openstack-request-id Implements: blueprint cross-service-request-id Change-Id: I5e370fd3de5ee2f8a8d13553015d88910ff5ea87
162 lines
5.8 KiB
Python
162 lines
5.8 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
|
|
import webob.dec
|
|
import webob.exc
|
|
|
|
from nova import context
|
|
from nova.openstack.common.gettextutils import _
|
|
from nova.openstack.common import jsonutils
|
|
from nova.openstack.common import log as logging
|
|
from nova.openstack.common.middleware import request_id
|
|
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: noauth or keystone.'),
|
|
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."""
|
|
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()
|
|
# NOTE (Alex Xu): This is just for configuration file compatibility.
|
|
# If the configuration file still contains 'ratelimit_v3', just ignore it.
|
|
# We will remove this code at next release (J)
|
|
if 'ratelimit_v3' in pipeline:
|
|
LOG.warn(_('ratelimit_v3 is removed from v3 api.'))
|
|
pipeline.remove('ratelimit_v3')
|
|
return _load_pipeline(loader, pipeline)
|
|
|
|
|
|
def pipeline_factory_v3(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())
|
|
|
|
|
|
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.'))
|
|
|
|
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)
|
|
|
|
req.environ['nova.context'] = ctx
|
|
return self.application
|
|
|
|
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(_("Sourcing roles from deprecated X-Role HTTP "
|
|
"header"))
|
|
return [r.strip() for r in roles.split(',')]
|