# Copyright 2013 - Mirantis, Inc. # Copyright 2016 - Brocade Communications Systems, 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 base64 from mistral_lib.actions import context as lib_ctx from mistral_lib import serialization from oslo_config import cfg from oslo_context import context as oslo_context from oslo_log import log as logging import oslo_messaging as messaging from oslo_serialization import jsonutils from osprofiler import profiler import pecan from pecan import hooks from mistral import auth from mistral import exceptions as exc from mistral_lib import utils LOG = logging.getLogger(__name__) CONF = cfg.CONF _CTX_THREAD_LOCAL_NAME = "MISTRAL_APP_CTX_THREAD_LOCAL" ALLOWED_WITHOUT_AUTH = [ '/', '/info', '/v2/', '/workflowv2/', '/workflowv2/v2/' ] class MistralContext(oslo_context.RequestContext): FROM_DICT_EXTRA_KEYS = ['root_execution_id'] def __init__(self, auth_uri=None, auth_cacert=None, insecure=False, service_catalog=None, region_name=None, is_trust_scoped=False, redelivered=False, expires_at=None, trust_id=None, is_target=False, root_execution_id=None, **kwargs): self.auth_uri = auth_uri self.auth_cacert = auth_cacert self.insecure = insecure self.service_catalog = service_catalog self.region_name = region_name self.is_trust_scoped = is_trust_scoped self.redelivered = redelivered self.expires_at = expires_at self.trust_id = trust_id self.is_target = is_target self.root_execution_id = root_execution_id # We still use Mistral thread local variable. Maybe could consider # using the variable provided by oslo_context in future. super(MistralContext, self).__init__(overwrite=False, **kwargs) def get_logging_values(self): ctx_dict = super(MistralContext, self).get_logging_values() ctx_dict.update( { 'root_execution_id': self.root_execution_id, } ) return ctx_dict def update_store(self): super(MistralContext, self).update_store() def to_dict(self): """Return a dictionary of context attributes.""" ctx_dict = super(MistralContext, self).to_dict() ctx_dict.update( { 'user_name': self.user_name, 'project_name': self.project_name, 'domain_name': self.domain_name, 'user_domain_name': self.user_domain_name, 'project_domain_name': self.project_domain_name, 'auth_uri': self.auth_uri, 'auth_cacert': self.auth_cacert, 'insecure': self.insecure, 'service_catalog': self.service_catalog, 'region_name': self.region_name, 'is_trust_scoped': self.is_trust_scoped, 'redelivered': self.redelivered, 'expires_at': self.expires_at, 'trust_id': self.trust_id, 'is_target': self.is_target, 'root_execution_id': self.root_execution_id, } ) return ctx_dict @classmethod def from_dict(cls, values, **kwargs): """Construct a context object from a provided dictionary.""" kwargs.setdefault('auth_uri', values.get('auth_uri')) kwargs.setdefault('auth_cacert', values.get('auth_cacert')) kwargs.setdefault('insecure', values.get('insecure', False)) kwargs.setdefault('service_catalog', values.get('service_catalog')) kwargs.setdefault('region_name', values.get('region_name')) kwargs.setdefault( 'is_trust_scoped', values.get('is_trust_scoped', False) ) kwargs.setdefault('redelivered', values.get('redelivered', False)) kwargs.setdefault('expires_at', values.get('expires_at')) kwargs.setdefault('trust_id', values.get('trust_id')) kwargs.setdefault('is_target', values.get('is_target', False)) kwargs.setdefault('root_execution_id', values.get('root_execution_id')) return super(MistralContext, cls).from_dict(values, **kwargs) @classmethod def from_environ(cls, headers, env): kwargs = _extract_mistral_auth_params(headers) token_info = env.get('keystone.token_info', {}) if not kwargs['is_target']: kwargs['service_catalog'] = token_info.get('token', {}) kwargs['expires_at'] = (token_info['token']['expires_at'] if token_info else None) context = super(MistralContext, cls).from_environ(env, **kwargs) context.is_admin = True if 'admin' in context.roles else False return context def has_ctx(): return utils.has_thread_local(_CTX_THREAD_LOCAL_NAME) def ctx(root_execution_id=None): if not has_ctx(): raise exc.ApplicationContextNotFoundException() context = utils.get_thread_local(_CTX_THREAD_LOCAL_NAME) if root_execution_id: context.root_execution_id = root_execution_id set_ctx(context) return context def set_ctx(new_ctx): if new_ctx: new_ctx.update_store() utils.set_thread_local(_CTX_THREAD_LOCAL_NAME, new_ctx) def _extract_mistral_auth_params(headers): service_catalog = None if headers.get("X-Target-Auth-Uri"): insecure_header = headers.get('X-Target-Insecure', 'False') if insecure_header == 'False': insecure = False elif insecure_header == 'True': insecure = True else: raise (exc.MistralException( 'X-Target-Insecure must be either "True", "False" or not ' 'provided. The default is "False".')) params = { # TODO(akovi): Target cert not handled yet 'auth_cacert': None, 'insecure': insecure, 'auth_token': headers.get('X-Target-Auth-Token'), 'auth_uri': headers.get('X-Target-Auth-Uri'), 'project_id': headers.get('X-Target-Project-Id'), 'user': headers.get('X-Target-User-Id'), 'user_name': headers.get('X-Target-User-Name'), 'region_name': headers.get('X-Target-Region-Name'), 'is_target': True } if not params['auth_token']: raise (exc.MistralException( 'Target auth URI (X-Target-Auth-Uri) target auth token ' '(X-Target-Auth-Token) must be present')) # It's possible that target service catalog is not provided, in this # case, Mistral needs to get target service catalog dynamically when # talking to target openstack deployment later on. service_catalog = _extract_service_catalog_from_headers( headers ) else: params = { 'auth_uri': CONF.keystone_authtoken.www_authenticate_uri, 'auth_cacert': CONF.keystone_authtoken.cafile, 'insecure': False, 'region_name': headers.get('X-Region-Name'), 'is_target': False } params['service_catalog'] = service_catalog return params def _extract_service_catalog_from_headers(headers): target_service_catalog_header = headers.get( 'X-Target-Service-Catalog') if target_service_catalog_header: decoded_catalog = base64.b64decode( target_service_catalog_header).decode() return jsonutils.loads(decoded_catalog) else: return None class RpcContextSerializer(messaging.Serializer): def __init__(self, entity_serializer=None): self.entity_serializer = ( entity_serializer or serialization.get_polymorphic_serializer() ) def serialize_entity(self, context, entity): if not self.entity_serializer: return entity return self.entity_serializer.serialize(entity) def deserialize_entity(self, context, entity): if not self.entity_serializer: return entity return self.entity_serializer.deserialize(entity) def serialize_context(self, context): ctx = context.to_dict() pfr = profiler.get() if pfr: ctx['trace_info'] = { "hmac_key": pfr.hmac_key, "base_id": pfr.get_base_id(), "parent_id": pfr.get_id() } return ctx def deserialize_context(self, context): trace_info = context.pop('trace_info', None) if trace_info: profiler.init(**trace_info) ctx = MistralContext.from_dict(context) set_ctx(ctx) return ctx class AuthHook(hooks.PecanHook): def before(self, state): if state.request.path in ALLOWED_WITHOUT_AUTH: return if not CONF.pecan.auth_enable: return try: auth_handler = auth.get_auth_handler() auth_handler.authenticate(state.request) except Exception as e: msg = "Failed to validate access token: %s" % str(e) LOG.exception(msg) pecan.abort( status_code=401, detail=msg, headers={'Server-Error-Message': msg, "WWW-Authenticate": msg} ) class ContextHook(hooks.PecanHook): def before(self, state): context = MistralContext.from_environ( state.request.headers, state.request.environ ) set_ctx(context) def after(self, state): set_ctx(None) def create_action_context(execution_ctx): context = ctx() security_ctx = lib_ctx.SecurityContext( auth_cacert=context.auth_cacert, auth_token=context.auth_token, auth_uri=context.auth_uri, expires_at=context.expires_at, insecure=context.insecure, is_target=context.is_target, is_trust_scoped=context.is_trust_scoped, project_id=context.project_id, project_name=context.project_name, user_name=context.user_name, redelivered=context.redelivered, region_name=context.region_name, service_catalog=context.service_catalog, trust_id=context.trust_id, ) ex_ctx = lib_ctx.ExecutionContext(**execution_ctx) return lib_ctx.ActionContext(security_ctx, ex_ctx)