summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cinder/api/middleware/auth.py92
-rw-r--r--cinder/api/middleware/request_id.py71
-rw-r--r--etc/cinder/api-paste.ini27
-rw-r--r--releasenotes/notes/fix-request_id-6f6972b2c12d1a18.yaml15
4 files changed, 144 insertions, 61 deletions
diff --git a/cinder/api/middleware/auth.py b/cinder/api/middleware/auth.py
index caf36f659..9758d2828 100644
--- a/cinder/api/middleware/auth.py
+++ b/cinder/api/middleware/auth.py
@@ -22,7 +22,6 @@ import os
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
@@ -60,6 +59,36 @@ def pipeline_factory(loader, global_conf, **local_conf):
return app
+def _set_request_context(req, **kwargs):
+ """Sets request context based on parameters and request."""
+ remote_address = getattr(req, 'remote_address', '127.0.0.1')
+
+ 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(
+ explanation=_('Invalid service catalog json.'))
+
+ if CONF.use_forwarded_for:
+ remote_address = req.headers.get('X-Forwarded-For', remote_address)
+
+ kwargs.setdefault('remote_address', remote_address)
+ kwargs.setdefault('service_catalog', service_catalog)
+
+ # Preserve the timestamp set by the RequestId middleware
+ kwargs['timestamp'] = getattr(req.environ.get('cinder.context'),
+ 'timestamp',
+ None)
+
+ # request ID and global ID are present in the environment req.environ
+ ctx = context.RequestContext.from_environ(req.environ, **kwargs)
+ req.environ['cinder.context'] = ctx
+ return ctx
+
+
class InjectContext(base_wsgi.Middleware):
"""Add a 'cinder.context' to WSGI environ."""
@@ -75,53 +104,25 @@ class InjectContext(base_wsgi.Middleware):
class CinderKeystoneContext(base_wsgi.Middleware):
"""Make a request context from keystone headers."""
+ ENV_OVERWRITES = {
+ 'X_PROJECT_DOMAIN_ID': 'project_domain_id',
+ 'X_PROJECT_DOMAIN_NAME': 'project_domain_name',
+ 'X_USER_DOMAIN_ID': 'user_domain_id',
+ 'X_USER_DOMAIN_NAME': 'user_domain_name',
+ }
@webob.dec.wsgify(RequestClass=base_wsgi.Request)
def __call__(self, req):
-
- # NOTE(jamielennox): from_environ handles these in newer versions
- project_name = req.headers.get('X_TENANT_NAME')
- req_id = req.environ.get(request_id.ENV_REQUEST_ID)
-
- # Build a context, including the auth_token...
- remote_address = req.remote_addr
-
- 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(
- explanation=_('Invalid service catalog json.'))
-
- if CONF.use_forwarded_for:
- remote_address = req.headers.get('X-Forwarded-For', remote_address)
-
- ctx = context.RequestContext.from_environ(
- req.environ,
- request_id=req_id,
- remote_address=remote_address,
- project_name=project_name,
- service_catalog=service_catalog)
+ params = {'project_name': req.headers.get('X_TENANT_NAME')}
+ for env_name, param_name in self.ENV_OVERWRITES.items():
+ if req.environ.get(env_name):
+ params[param_name] = req.environ[env_name]
+ ctx = _set_request_context(req, **params)
if ctx.user_id is None:
LOG.debug("Neither X_USER_ID nor X_USER found in request")
return webob.exc.HTTPUnauthorized()
- if req.environ.get('X_PROJECT_DOMAIN_ID'):
- ctx.project_domain_id = req.environ['X_PROJECT_DOMAIN_ID']
-
- if req.environ.get('X_PROJECT_DOMAIN_NAME'):
- ctx.project_domain_name = req.environ['X_PROJECT_DOMAIN_NAME']
-
- if req.environ.get('X_USER_DOMAIN_ID'):
- ctx.user_domain_id = req.environ['X_USER_DOMAIN_ID']
-
- if req.environ.get('X_USER_DOMAIN_NAME'):
- ctx.user_domain_name = req.environ['X_USER_DOMAIN_NAME']
-
- req.environ['cinder.context'] = ctx
return self.application
@@ -149,15 +150,8 @@ class NoAuthMiddlewareBase(base_wsgi.Middleware):
token = req.headers['X-Auth-Token']
user_id, _sep, project_id = token.partition(':')
project_id = project_id or user_id
- remote_address = getattr(req, 'remote_address', '127.0.0.1')
- if CONF.use_forwarded_for:
- remote_address = req.headers.get('X-Forwarded-For', remote_address)
- ctx = context.RequestContext(user_id,
- project_id,
- is_admin=True,
- remote_address=remote_address)
-
- req.environ['cinder.context'] = ctx
+ _set_request_context(req, user_id=user_id, project_id=project_id,
+ is_admin=True)
return self.application
diff --git a/cinder/api/middleware/request_id.py b/cinder/api/middleware/request_id.py
new file mode 100644
index 000000000..87d938808
--- /dev/null
+++ b/cinder/api/middleware/request_id.py
@@ -0,0 +1,71 @@
+# Copyright 2022 Red Hat, Inc.
+# All Rights Reserved.
+#
+# 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.
+
+from oslo_log import log as logging
+from oslo_middleware import request_id as oslo_req_id
+from oslo_utils import timeutils
+
+from cinder import context as cinder_context
+
+
+LOG = logging.getLogger(__name__)
+
+
+class RequestId(oslo_req_id.RequestId):
+ def _context_setter(self, environ, *args, **kwargs):
+ """Wrapper to set a temporary context.
+
+ It is necessary to replace the previous request's context, but at this
+ point when we are generating the new request ID we don't have the
+ keystone info, so use a placeholder with the information we have:
+
+ - global_request_id ==> Extracted from the headers by the parent class
+ - request_id => Generated by the parent class __call__ method
+ - timestamp => The moment Cinder API starts processing the request
+
+ This middleware needs to be the first in ALL the pipelines for things
+ to work as expected, otherwise we'll have the following issues:
+
+ - Logs from other filters reuse a context from a previous request,
+ presenting the wrong request id and project and user info, and then
+ after the request passes the auth filter the request id will change
+ to the right one. We'll see this when enabling debug mode in the
+ keystonemiddleware module.
+
+ - Requests that don't require authorization (/ and /healthcheck) won't
+ return a request ID in the headers.
+ """
+ # Replace previous request's context with all the info we have now
+ placeholder_ctx = cinder_context.RequestContext(
+ request_id=environ[oslo_req_id.ENV_REQUEST_ID],
+ global_request_id=environ.get(oslo_req_id.GLOBAL_REQ_ID),
+ timestamp=timeutils.utcnow(),
+ )
+
+ # Only update environ, oslo_context local store was updated
+ # automatically when instantiating the request context.
+ environ['cinder.context'] = placeholder_ctx
+
+ # Have a timestamped log with the start of the pipeline processing.
+ LOG.debug('RequestId filter calling following filter/app')
+ return self._application(environ, *args, **kwargs)
+
+ def __init__(self, *args, **kwargs):
+ # Parent __call__ method creates the request id and makes the call to
+ # the chained app one after the other, so we need a wrapper on the app
+ # to set the context.
+ super().__init__(*args, **kwargs)
+ self._application = self.application
+ self.application = self._context_setter
diff --git a/etc/cinder/api-paste.ini b/etc/cinder/api-paste.ini
index ce2e0ea98..cd3040e11 100644
--- a/etc/cinder/api-paste.ini
+++ b/etc/cinder/api-paste.ini
@@ -10,13 +10,10 @@ use = call:cinder.api:root_app_factory
[composite:openstack_volume_api_v3]
use = call:cinder.api.middleware.auth:pipeline_factory
-noauth = cors http_proxy_to_wsgi request_id faultwrap sizelimit osprofiler noauth apiv3
-noauth_include_project_id = cors http_proxy_to_wsgi request_id faultwrap sizelimit osprofiler noauth_include_project_id apiv3
-keystone = cors http_proxy_to_wsgi request_id faultwrap sizelimit osprofiler authtoken keystonecontext apiv3
-keystone_nolimit = cors http_proxy_to_wsgi request_id faultwrap sizelimit osprofiler authtoken keystonecontext apiv3
-
-[filter:request_id]
-paste.filter_factory = oslo_middleware.request_id:RequestId.factory
+noauth = request_id cors http_proxy_to_wsgi faultwrap sizelimit osprofiler noauth apiv3
+noauth_include_project_id = request_id cors http_proxy_to_wsgi faultwrap sizelimit osprofiler noauth_include_project_id apiv3
+keystone = request_id cors http_proxy_to_wsgi faultwrap sizelimit osprofiler authtoken keystonecontext apiv3
+keystone_nolimit = request_id cors http_proxy_to_wsgi faultwrap sizelimit osprofiler authtoken keystonecontext apiv3
[filter:http_proxy_to_wsgi]
paste.filter_factory = oslo_middleware.http_proxy_to_wsgi:HTTPProxyToWSGI.factory
@@ -44,11 +41,19 @@ paste.filter_factory = oslo_middleware.sizelimit:RequestBodySizeLimiter.factory
paste.app_factory = cinder.api.v3.router:APIRouter.factory
[pipeline:apiversions]
-pipeline = cors http_proxy_to_wsgi faultwrap osvolumeversionapp
+pipeline = request_id cors http_proxy_to_wsgi faultwrap osvolumeversionapp
[app:osvolumeversionapp]
paste.app_factory = cinder.api.versions:Versions.factory
+[pipeline:healthcheck]
+pipeline = request_id healthcheckapp
+
+[app:healthcheckapp]
+paste.app_factory = oslo_middleware:Healthcheck.app_factory
+backends = disable_by_file
+disable_by_file_path = /etc/cinder/healthcheck_disable
+
##########
# Shared #
##########
@@ -59,7 +64,5 @@ paste.filter_factory = cinder.api.middleware.auth:CinderKeystoneContext.factory
[filter:authtoken]
paste.filter_factory = keystonemiddleware.auth_token:filter_factory
-[app:healthcheck]
-paste.app_factory = oslo_middleware:Healthcheck.app_factory
-backends = disable_by_file
-disable_by_file_path = /etc/cinder/healthcheck_disable
+[filter:request_id]
+paste.filter_factory = cinder.api.middleware.request_id:RequestId.factory
diff --git a/releasenotes/notes/fix-request_id-6f6972b2c12d1a18.yaml b/releasenotes/notes/fix-request_id-6f6972b2c12d1a18.yaml
new file mode 100644
index 000000000..f610fb263
--- /dev/null
+++ b/releasenotes/notes/fix-request_id-6f6972b2c12d1a18.yaml
@@ -0,0 +1,15 @@
+---
+fixes:
+ - |
+ `Bug #1960019 <https://bugs.launchpad.net/cinder/+bug/1960019>`_: Fixed
+ value of the x-openstack-request-id header when Cinder is using noauth.
+ - |
+ `Bug #1960020 <https://bugs.launchpad.net/cinder/+bug/1960020>`_: Fixed
+ duplicated request-id values in logs for different requests, happens only
+ on request to / to get available api versions.
+ - |
+ `Bug #1960021 <https://bugs.launchpad.net/cinder/+bug/1960021>`_: Fixed
+ missing request id headers in requests to / to get available api versions.
+ - |
+ `Bug #1960329 <https://bugs.launchpad.net/cinder/+bug/1960329>`_: Fixed
+ wrong request ID on middleware filters.