Example #1
0
    def _add_to_revocation_list(self, data, lock):
        filtered_list = []
        revoked_token_data = {}

        current_time = self._get_current_time()
        expires = data['expires']

        if isinstance(expires, six.string_types):
            expires = timeutils.parse_isotime(expires)

        expires = timeutils.normalize_time(expires)

        if expires < current_time:
            LOG.warning(
                _LW('Token `%s` is expired, not adding to the '
                    'revocation list.'), data['id'])
            return

        revoked_token_data['expires'] = timeutils.isotime(expires,
                                                          subsecond=True)
        revoked_token_data['id'] = data['id']

        token_list = self._get_key_or_default(self.revocation_key, default=[])
        if not isinstance(token_list, list):
            # NOTE(morganfainberg): In the case that the revocation list is not
            # in a format we understand, reinitialize it. This is an attempt to
            # not allow the revocation list to be completely broken if
            # somehow the key is changed outside of keystone (e.g. memcache
            # that is shared by multiple applications). Logging occurs at error
            # level so that the cloud administrators have some awareness that
            # the revocation_list needed to be cleared out. In all, this should
            # be recoverable. Keystone cannot control external applications
            # from changing a key in some backends, however, it is possible to
            # gracefully handle and notify of this event.
            LOG.error(
                _LE('Reinitializing revocation list due to error '
                    'in loading revocation list from backend.  '
                    'Expected `list` type got `%(type)s`. Old '
                    'revocation list data: %(list)r'), {
                        'type': type(token_list),
                        'list': token_list
                    })
            token_list = []

        # NOTE(morganfainberg): on revocation, cleanup the expired entries, try
        # to keep the list of tokens revoked at the minimum.
        for token_data in token_list:
            try:
                expires_at = timeutils.normalize_time(
                    timeutils.parse_isotime(token_data['expires']))
            except ValueError:
                LOG.warning(
                    _LW('Removing `%s` from revocation list due to '
                        'invalid expires data in revocation list.'),
                    token_data.get('id', 'INVALID_TOKEN_DATA'))
                continue
            if expires_at > current_time:
                filtered_list.append(token_data)
        filtered_list.append(revoked_token_data)
        self._set_key(self.revocation_key, filtered_list, lock)
Example #2
0
    def _build_token_auth_context(self, request, token_id):
        if CONF.admin_token and token_id == CONF.admin_token:
            versionutils.report_deprecated_feature(
                LOG,
                _LW('build_auth_context middleware checking for the admin '
                    'token is deprecated as of the Mitaka release and will be '
                    'removed in the O release. If your deployment requires '
                    'use of the admin token, update keystone-paste.ini so '
                    'that admin_token_auth is before build_auth_context in '
                    'the paste pipelines, otherwise remove the '
                    'admin_token_auth middleware from the paste pipelines.'))
            return {}, True

        context = {'token_id': token_id}
        context['environment'] = request.environ

        try:
            token_ref = token_model.KeystoneToken(
                token_id=token_id,
                token_data=self.token_provider_api.validate_token(token_id))
            # TODO(gyee): validate_token_bind should really be its own
            # middleware
            wsgi.validate_token_bind(context, token_ref)
            return authorization.token_to_auth_context(token_ref), False
        except exception.TokenNotFound:
            LOG.warning(_LW('RBAC: Invalid token'))
            raise exception.Unauthorized()
Example #3
0
    def _build_token_auth_context(self, request, token_id):
        if CONF.admin_token and token_id == CONF.admin_token:
            versionutils.report_deprecated_feature(
                LOG,
                _LW('build_auth_context middleware checking for the admin '
                    'token is deprecated as of the Mitaka release and will be '
                    'removed in the O release. If your deployment requires '
                    'use of the admin token, update keystone-paste.ini so '
                    'that admin_token_auth is before build_auth_context in '
                    'the paste pipelines, otherwise remove the '
                    'admin_token_auth middleware from the paste pipelines.'))
            return {}, True

        context = {'token_id': token_id}
        context['environment'] = request.environ

        try:
            token_ref = token_model.KeystoneToken(
                token_id=token_id,
                token_data=self.token_provider_api.validate_token(token_id))
            # TODO(gyee): validate_token_bind should really be its own
            # middleware
            wsgi.validate_token_bind(context, token_ref)
            return authorization.token_to_auth_context(token_ref), False
        except exception.TokenNotFound:
            LOG.warning(_LW('RBAC: Invalid token'))
            raise exception.Unauthorized()
Example #4
0
    def _get_domain_id_for_list_request(self, context):
        """Get the domain_id for a v3 list call.

        If we running with multiple domain drivers, then the caller must
        specify a domain_id either as a filter or as part of the token scope.

        """
        if not CONF.identity.domain_specific_drivers_enabled:
            # We don't need to specify a domain ID in this case
            return

        if context['query_string'].get('domain_id') is not None:
            return context['query_string'].get('domain_id')

        try:
            token_ref = token_model.KeystoneToken(
                token_id=context['token_id'],
                token_data=self.token_provider_api.validate_token(
                    context['token_id']))
        except KeyError:
            raise exception.ValidationError(
                _('domain_id is required as part of entity'))
        except (exception.TokenNotFound,
                exception.UnsupportedTokenVersionException):
            LOG.warning(
                _LW('Invalid token found while getting domain ID '
                    'for list request'))
            raise exception.Unauthorized()

        if token_ref.domain_scoped:
            return token_ref.domain_id
        else:
            LOG.warning(
                _LW('No domain information specified as part of list request'))
            raise exception.Unauthorized()
Example #5
0
    def _get_domain_id_for_list_request(self, context):
        """Get the domain_id for a v3 list call.

        If we running with multiple domain drivers, then the caller must
        specify a domain_id either as a filter or as part of the token scope.

        """
        if not CONF.identity.domain_specific_drivers_enabled:
            # We don't need to specify a domain ID in this case
            return

        if context['query_string'].get('domain_id') is not None:
            return context['query_string'].get('domain_id')

        try:
            token_ref = token_model.KeystoneToken(
                token_id=context['token_id'],
                token_data=self.token_provider_api.validate_token(
                    context['token_id']))
        except KeyError:
            raise exception.ValidationError(
                _('domain_id is required as part of entity'))
        except (exception.TokenNotFound,
                exception.UnsupportedTokenVersionException):
            LOG.warning(_LW('Invalid token found while getting domain ID '
                            'for list request'))
            raise exception.Unauthorized()

        if token_ref.domain_scoped:
            return token_ref.domain_id
        else:
            LOG.warning(
                _LW('No domain information specified as part of list request'))
            raise exception.Unauthorized()
Example #6
0
    def _check_and_set_default_scoping(self, auth_info, auth_context):
        (domain_id, project_id, trust, unscoped) = auth_info.get_scope()
        if trust:
            project_id = trust["project_id"]
        if domain_id or project_id or trust:
            # scope is specified
            return

        # Skip scoping when unscoped federated token is being issued
        if federation.IDENTITY_PROVIDER in auth_context:
            return

        # Do not scope if request is for explicitly unscoped token
        if unscoped is not None:
            return

        # fill in default_project_id if it is available
        try:
            user_ref = self.identity_api.get_user(auth_context["user_id"])
        except exception.UserNotFound as e:
            LOG.exception(six.text_type(e))
            raise exception.Unauthorized(e)

        default_project_id = user_ref.get("default_project_id")
        if not default_project_id:
            # User has no default project. He shall get an unscoped token.
            return

        # make sure user's default project is legit before scoping to it
        try:
            default_project_ref = self.resource_api.get_project(default_project_id)
            default_project_domain_ref = self.resource_api.get_domain(default_project_ref["domain_id"])
            if default_project_ref.get("enabled", True) and default_project_domain_ref.get("enabled", True):
                if self.assignment_api.get_roles_for_user_and_project(user_ref["id"], default_project_id):
                    auth_info.set_scope(project_id=default_project_id)
                else:
                    msg = _LW(
                        "User %(user_id)s doesn't have access to"
                        " default project %(project_id)s. The token"
                        " will be unscoped rather than scoped to the"
                        " project."
                    )
                    LOG.warning(msg, {"user_id": user_ref["id"], "project_id": default_project_id})
            else:
                msg = _LW(
                    "User %(user_id)s's default project %(project_id)s"
                    " is disabled. The token will be unscoped rather"
                    " than scoped to the project."
                )
                LOG.warning(msg, {"user_id": user_ref["id"], "project_id": default_project_id})
        except (exception.ProjectNotFound, exception.DomainNotFound):
            # default project or default project domain doesn't exist,
            # will issue unscoped token instead
            msg = _LW(
                "User %(user_id)s's default project %(project_id)s not"
                " found. The token will be unscoped rather than"
                " scoped to the project."
            )
            LOG.warning(msg, {"user_id": user_ref["id"], "project_id": default_project_id})
Example #7
0
    def _add_to_revocation_list(self, data, lock):
        filtered_list = []
        revoked_token_data = {}

        current_time = self._get_current_time()
        expires = data['expires']

        if isinstance(expires, six.string_types):
            expires = timeutils.parse_isotime(expires)

        expires = timeutils.normalize_time(expires)

        if expires < current_time:
            LOG.warning(_LW('Token `%s` is expired, not adding to the '
                            'revocation list.'), data['id'])
            return

        revoked_token_data['expires'] = utils.isotime(expires,
                                                      subsecond=True)
        revoked_token_data['id'] = data['id']

        token_list = self._get_key_or_default(self.revocation_key, default=[])
        if not isinstance(token_list, list):
            # NOTE(morganfainberg): In the case that the revocation list is not
            # in a format we understand, reinitialize it. This is an attempt to
            # not allow the revocation list to be completely broken if
            # somehow the key is changed outside of keystone (e.g. memcache
            # that is shared by multiple applications). Logging occurs at error
            # level so that the cloud administrators have some awareness that
            # the revocation_list needed to be cleared out. In all, this should
            # be recoverable. Keystone cannot control external applications
            # from changing a key in some backends, however, it is possible to
            # gracefully handle and notify of this event.
            LOG.error(_LE('Reinitializing revocation list due to error '
                          'in loading revocation list from backend.  '
                          'Expected `list` type got `%(type)s`. Old '
                          'revocation list data: %(list)r'),
                      {'type': type(token_list), 'list': token_list})
            token_list = []

        # NOTE(morganfainberg): on revocation, cleanup the expired entries, try
        # to keep the list of tokens revoked at the minimum.
        for token_data in token_list:
            try:
                expires_at = timeutils.normalize_time(
                    timeutils.parse_isotime(token_data['expires']))
            except ValueError:
                LOG.warning(_LW('Removing `%s` from revocation list due to '
                                'invalid expires data in revocation list.'),
                            token_data.get('id', 'INVALID_TOKEN_DATA'))
                continue
            if expires_at > current_time:
                filtered_list.append(token_data)
        filtered_list.append(revoked_token_data)
        self._set_key(self.revocation_key, filtered_list, lock)
Example #8
0
 def main(cls):
     versionutils.report_deprecated_feature(
         LOG,
         _LW("keystone-manage pki_setup is deprecated as of Mitaka in "
             "favor of not using PKI tokens and may be removed in 'O' "
             "release."))
     LOG.warning(_LW('keystone-manage pki_setup is not recommended for '
                     'production use.'))
     keystone_user_id, keystone_group_id = cls.get_user_group()
     conf_pki = openssl.ConfigurePKI(keystone_user_id, keystone_group_id,
                                     rebuild=CONF.command.rebuild)
     conf_pki.run()
Example #9
0
 def main(cls):
     versionutils.report_deprecated_feature(
         LOG,
         _LW("keystone-manage pki_setup is deprecated as of Mitaka in "
             "favor of not using PKI tokens and may be removed in 'O' "
             "release."))
     LOG.warning(_LW('keystone-manage pki_setup is not recommended for '
                     'production use.'))
     keystone_user_id, keystone_group_id = cls.get_user_group()
     conf_pki = openssl.ConfigurePKI(keystone_user_id, keystone_group_id,
                                     rebuild=CONF.command.rebuild)
     conf_pki.run()
Example #10
0
    def process_request(self, request):

        # The request context stores itself in thread-local memory for logging.
        request_context = oslo_context.RequestContext(
            request_id=request.environ.get('openstack.request_id'))

        if authorization.AUTH_CONTEXT_ENV in request.environ:
            msg = _LW('Auth context already exists in the request '
                      'environment; it will be used for authorization '
                      'instead of creating a new one.')
            LOG.warning(msg)
            return

        auth_context, token_id, is_admin = self._build_auth_context(request)

        request_context.auth_token = token_id
        request_context.is_admin = is_admin

        if auth_context is None:
            # The client didn't send any auth info, so don't set auth context.
            return

        # The attributes of request_context are put into the logs. This is a
        # common pattern for all the OpenStack services. In all the other
        # projects these are IDs, so set the attributes to IDs here rather than
        # the name.
        request_context.user = auth_context.get('user_id')
        request_context.tenant = auth_context.get('project_id')
        request_context.domain = auth_context.get('domain_id')
        request_context.user_domain = auth_context.get('user_domain_id')
        request_context.project_domain = auth_context.get('project_domain_id')
        request_context.update_store()

        LOG.debug('RBAC: auth_context: %s', auth_context)
        request.environ[authorization.AUTH_CONTEXT_ENV] = auth_context
Example #11
0
    def _get_domain_id_from_token(self, context):
        """Get the domain_id for a v3 create call.

        In the case of a v3 create entity call that does not specify a domain
        ID, the spec says that we should use the domain scoping from the token
        being used.

        """
        # We could make this more efficient by loading the domain_id
        # into the context in the wrapper function above (since
        # this version of normalize_domain will only be called inside
        # a v3 protected call).  However, this optimization is probably not
        # worth the duplication of state
        try:
            token_ref = token_model.KeystoneToken(
                token_id=context["token_id"], token_data=self.token_provider_api.validate_token(context["token_id"])
            )
        except KeyError:
            # This might happen if we use the Admin token, for instance
            raise exception.ValidationError(_("A domain-scoped token must be used"))
        except (exception.TokenNotFound, exception.UnsupportedTokenVersionException):
            LOG.warning(_LW("Invalid token found while getting domain ID " "for create request"))
            raise exception.Unauthorized()

        if token_ref.domain_scoped:
            return token_ref.domain_id
        else:
            # TODO(henry-nash): We should issue an exception here since if
            # a v3 call does not explicitly specify the domain_id in the
            # entity, it should be using a domain scoped token.  However,
            # the current tempest heat tests issue a v3 call without this.
            # This is raised as bug #1283539.  Once this is fixed, we
            # should remove the line below and replace it with an error.
            return CONF.identity.default_domain_id
Example #12
0
def deprecation_warning():
    versionutils.report_deprecated_feature(
        LOG,
        _LW('httpd/keystone.py is deprecated as of Mitaka'
            ' in favor of keystone-wsgi-admin and keystone-wsgi-public'
            ' and may be removed in O.')
    )
Example #13
0
def checkout_vendor(repo, rev):
    # TODO(termie): this function is a good target for some optimizations :PERF
    name = repo.split('/')[-1]
    if name.endswith('.git'):
        name = name[:-4]

    working_dir = os.getcwd()
    revdir = os.path.join(VENDOR, '%s-%s' % (name, rev.replace('/', '_')))
    modcheck = os.path.join(VENDOR, '.%s-%s' % (name, rev.replace('/', '_')))
    try:
        if os.path.exists(modcheck):
            mtime = os.stat(modcheck).st_mtime
            if int(time.time()) - mtime < 10000:
                return revdir

        if not os.path.exists(revdir):
            utils.git('clone', repo, revdir)

        os.chdir(revdir)
        utils.git('checkout', '-q', 'master')
        utils.git('pull', '-q')
        utils.git('checkout', '-q', rev)

        # write out a modified time
        with open(modcheck, 'w') as fd:
            fd.write('1')
    except environment.subprocess.CalledProcessError:
        LOG.warning(_LW('Failed to checkout %s'), repo)
    os.chdir(working_dir)
    return revdir
Example #14
0
    def read_domain_configs_from_files(self):
        """Read configs from file(s) and load into database.

        The command line parameters have already been parsed and the CONF
        command option will have been set. It is either set to the name of an
        explicit domain, or it's None to indicate that we want all domain
        config files.

        """
        domain_name = CONF.command.domain_name
        conf_dir = CONF.identity.domain_config_dir
        if not os.path.exists(conf_dir):
            print(_("Unable to locate domain config directory: %s") % conf_dir)
            raise ValueError

        if domain_name:
            # Request is to upload the configs for just one domain
            fname = DOMAIN_CONF_FHEAD + domain_name + DOMAIN_CONF_FTAIL
            self.upload_config_to_database(os.path.join(conf_dir, fname), domain_name)
            return

        # Request is to transfer all config files, so let's read all the
        # files in the config directory, and transfer those that match the
        # filename pattern of 'keystone.<domain_name>.conf'
        for r, d, f in os.walk(conf_dir):
            for fname in f:
                if fname.startswith(DOMAIN_CONF_FHEAD) and fname.endswith(DOMAIN_CONF_FTAIL):
                    if fname.count(".") >= 2:
                        self.upload_configs_to_database(
                            os.path.join(r, fname), fname[len(DOMAIN_CONF_FHEAD) : -len(DOMAIN_CONF_FTAIL)]
                        )
                    else:
                        LOG.warn(_LW("Ignoring file (%s) while scanning " "domain config directory"), fname)
Example #15
0
    def process_request(self, request):

        # The request context stores itself in thread-local memory for logging.
        oslo_context.RequestContext(
            request_id=request.environ.get('openstack.request_id'))

        if authorization.AUTH_CONTEXT_ENV in request.environ:
            msg = _LW('Auth context already exists in the request '
                      'environment; it will be used for authorization '
                      'instead of creating a new one.')
            LOG.warning(msg)
            return

        # NOTE(gyee): token takes precedence over SSL client certificates.
        # This will preserve backward compatibility with the existing
        # behavior. Tokenless authorization with X.509 SSL client
        # certificate is effectively disabled if no trusted issuers are
        # provided.
        if AUTH_TOKEN_HEADER in request.headers:
            auth_context = self._build_auth_context(request)
        elif self._validate_trusted_issuer(request.environ):
            auth_context = self._build_tokenless_auth_context(
                request.environ)
        else:
            LOG.debug('There is either no auth token in the request or '
                      'the certificate issuer is not trusted. No auth '
                      'context will be set.')
            return
        LOG.debug('RBAC: auth_context: %s', auth_context)
        request.environ[authorization.AUTH_CONTEXT_ENV] = auth_context
Example #16
0
    def _get_domain_id_from_token(self, context):
        """Get the domain_id for a v3 create call.

        In the case of a v3 create entity call that does not specify a domain
        ID, the spec says that we should use the domain scoping from the token
        being used.

        """
        token_ref = utils.get_token_ref(context)

        if token_ref.domain_scoped:
            return token_ref.domain_id
        else:
            # TODO(henry-nash): We should issue an exception here since if
            # a v3 call does not explicitly specify the domain_id in the
            # entity, it should be using a domain scoped token.  However,
            # the current tempest heat tests issue a v3 call without this.
            # This is raised as bug #1283539.  Once this is fixed, we
            # should remove the line below and replace it with an error.
            #
            # Ahead of actually changing the code to raise an exception, we
            # issue a deprecation warning.
            versionutils.report_deprecated_feature(
                LOG,
                _LW('Not specifying a domain during a create user, group or '
                    'project call, and relying on falling back to the '
                    'default domain, is deprecated as of Liberty and will be '
                    'removed in the N release. Specify the domain explicitly '
                    'or use a domain-scoped token'))
            return CONF.identity.default_domain_id
Example #17
0
 def delete_domain_assignments(self, domain_id):
     """Delete all assignments for a domain."""
     msg = _LW('delete_domain_assignments method not found in custom '
               'assignment driver. Domain assignments for domain (%s) to '
               'users from other domains will not be removed. This was '
               'added in V9 of the assignment driver.')
     LOG.warning(msg, domain_id)
Example #18
0
    def _transform(self, identity_values):
        """Transform local mappings, to an easier to understand format.

        Transform the incoming array to generate the return value for
        the process function. Generating content for Keystone tokens will
        be easier if some pre-processing is done at this level.

        :param identity_values: local mapping from valid evaluations
        :type identity_values: array of dict

        Example identity_values::

            [{'group': {'id': '0cd5e9'}, 'user': {'email': '*****@*****.**'}}]

        :returns: dictionary with user name and group_ids.

        """

        # initialize the group_ids as a set to eliminate duplicates
        user_name = None
        group_ids = set()

        for identity_value in identity_values:
            if 'user' in identity_value:
                # if a mapping outputs more than one user name, log it
                if user_name is not None:
                    LOG.warning(_LW('Ignoring user name %s'),
                                identity_value['user']['name'])
                else:
                    user_name = identity_value['user']['name']
            if 'group' in identity_value:
                group_ids.add(identity_value['group']['id'])

        return {'name': user_name, 'group_ids': list(group_ids)}
Example #19
0
    def create_key_directory(self,
                             keystone_user_id=None,
                             keystone_group_id=None):
        """Attempt to create the key directory if it doesn't exist."""
        if not os.access(self.key_repository, os.F_OK):
            LOG.info(
                _LI('key_repository does not appear to exist; attempting to '
                    'create it'))

            try:
                os.makedirs(self.key_repository, 0o700)
            except OSError:
                LOG.error(
                    _LE('Failed to create key_repository: either it already '
                        'exists or you don\'t have sufficient permissions to '
                        'create it'))

            if keystone_user_id and keystone_group_id:
                os.chown(self.key_repository, keystone_user_id,
                         keystone_group_id)
            elif keystone_user_id or keystone_group_id:
                LOG.warning(
                    _LW('Unable to change the ownership of key_repository without '
                        'a keystone user ID and keystone group ID both being '
                        'provided: %s') % self.key_repository)
Example #20
0
    def process_request(self, request):
        context_env = request.environ.get(core.CONTEXT_ENV, {})

        # NOTE(notmorgan): This code is merged over from the admin token
        # middleware and now emits the security warning when the
        # conf.admin_token value is set.
        token = request.headers.get(core.AUTH_TOKEN_HEADER)
        if CONF.admin_token and (token == CONF.admin_token):
            context_env['is_admin'] = True
            LOG.warning(
                _LW("The use of the '[DEFAULT] admin_token' configuration"
                    "option presents a significant security risk and should "
                    "not be set. This option is deprecated in favor of using "
                    "'keystone-manage bootstrap' and will be removed in a "
                    "future release."))
            request.environ[core.CONTEXT_ENV] = context_env

        if not context_env.get('is_admin', False):
            resp = super(AuthContextMiddleware, self).process_request(request)

            if resp:
                return resp

        # NOTE(jamielennox): function is split so testing can check errors from
        # fill_context. There is no actual reason for fill_context to raise
        # errors rather than return a resp, simply that this is what happened
        # before refactoring and it was easier to port. This can be fixed up
        # and the middleware_exceptions helper removed.
        self.fill_context(request)
Example #21
0
        def wrapper(self, context, **kwargs):
            if not context['is_admin']:
                action = 'identity:%s' % f.__name__
                creds = _build_policy_check_credentials(self, action,
                                                        context, kwargs)
                # Now, build the target dict for policy check.  We include:
                #
                # - Any query filter parameters
                # - Data from the main url (which will be in the kwargs
                #   parameter) and would typically include the prime key
                #   of a get/update/delete call
                #
                # First  any query filter parameters
                target = dict()
                if filters:
                    for item in filters:
                        if item in context['query_string']:
                            target[item] = context['query_string'][item]

                    LOG.debug('RBAC: Adding query filter params (%s)', (
                        ', '.join(['%s=%s' % (item, target[item])
                                  for item in target])))

                # Now any formal url parameters
                for key in kwargs:
                    target[key] = kwargs[key]

                self.policy_api.enforce(creds,
                                        action,
                                        utils.flatten_dict(target))

                LOG.debug('RBAC: Authorization granted')
            else:
                LOG.warning(_LW('RBAC: Bypassing authorization'))
            return f(self, context, filters, **kwargs)
    def _get_domain_id_from_token(self, context):
        """Get the domain_id for a v3 create call.

        In the case of a v3 create entity call that does not specify a domain
        ID, the spec says that we should use the domain scoping from the token
        being used.

        """
        token_ref = utils.get_token_ref(context)

        if token_ref.domain_scoped:
            return token_ref.domain_id
        else:
            # TODO(henry-nash): We should issue an exception here since if
            # a v3 call does not explicitly specify the domain_id in the
            # entity, it should be using a domain scoped token.  However,
            # the current tempest heat tests issue a v3 call without this.
            # This is raised as bug #1283539.  Once this is fixed, we
            # should remove the line below and replace it with an error.
            #
            # Ahead of actually changing the code to raise an exception, we
            # issue a deprecation warning.
            versionutils.report_deprecated_feature(
                LOG,
                _LW('Not specifying a domain during a create user, group or '
                    'project call, and relying on falling back to the '
                    'default domain, is deprecated as of Liberty and will be '
                    'removed in the N release. Specify the domain explicitly '
                    'or use a domain-scoped token'))
            return CONF.identity.default_domain_id
Example #23
0
    def process_request(self, request):

        # The request context stores itself in thread-local memory for logging.
        oslo_context.RequestContext(
            request_id=request.environ.get('openstack.request_id'))

        if authorization.AUTH_CONTEXT_ENV in request.environ:
            msg = _LW('Auth context already exists in the request '
                      'environment; it will be used for authorization '
                      'instead of creating a new one.')
            LOG.warning(msg)
            return

        # NOTE(gyee): token takes precedence over SSL client certificates.
        # This will preserve backward compatibility with the existing
        # behavior. Tokenless authorization with X.509 SSL client
        # certificate is effectively disabled if no trusted issuers are
        # provided.
        if AUTH_TOKEN_HEADER in request.headers:
            auth_context = self._build_auth_context(request)
        elif self._validate_trusted_issuer(request.environ):
            auth_context = self._build_tokenless_auth_context(request.environ)
        else:
            LOG.debug('There is either no auth token in the request or '
                      'the certificate issuer is not trusted. No auth '
                      'context will be set.')
            return
        LOG.debug('RBAC: auth_context: %s', auth_context)
        request.environ[authorization.AUTH_CONTEXT_ENV] = auth_context
Example #24
0
    def __init__(self, conf_obj, server_conf_obj, keystone_user,
                 keystone_group, rebuild, **kwargs):
        self.conf_dir = os.path.dirname(server_conf_obj.ca_certs)
        self.use_keystone_user = keystone_user
        self.use_keystone_group = keystone_group
        self.rebuild = rebuild
        self.ssl_config_file_name = os.path.join(self.conf_dir, "openssl.conf")
        self.request_file_name = os.path.join(self.conf_dir, "req.pem")
        self.ssl_dictionary = {
            'conf_dir': self.conf_dir,
            'ca_cert': server_conf_obj.ca_certs,
            'default_md': 'default',
            'ssl_config': self.ssl_config_file_name,
            'ca_private_key': conf_obj.ca_key,
            'request_file': self.request_file_name,
            'signing_key': server_conf_obj.keyfile,
            'signing_cert': server_conf_obj.certfile,
            'key_size': int(conf_obj.key_size),
            'valid_days': int(conf_obj.valid_days),
            'cert_subject': conf_obj.cert_subject
        }

        try:
            # OpenSSL 1.0 and newer support default_md = default,
            # older versions do not
            openssl_ver = environment.subprocess.Popen(
                ['openssl', 'version'],
                stdout=environment.subprocess.PIPE).stdout.read()
            if "OpenSSL 0." in openssl_ver:
                self.ssl_dictionary['default_md'] = 'sha1'
        except OSError:
            LOG.warn(
                _LW('Failed to invoke ``openssl version``, '
                    'assuming is v1.0 or newer'))
        self.ssl_dictionary.update(kwargs)
Example #25
0
    def _get_domain_id_from_token(self, context):
        """Get the domain_id for a v3 create call.

        In the case of a v3 create entity call that does not specify a domain
        ID, the spec says that we should use the domain scoping from the token
        being used.

        """
        # We could make this more efficient by loading the domain_id
        # into the context in the wrapper function above (since
        # this version of normalize_domain will only be called inside
        # a v3 protected call).  However, this optimization is probably not
        # worth the duplication of state
        try:
            token_ref = token_model.KeystoneToken(
                token_id=context['token_id'],
                token_data=self.token_provider_api.validate_token(
                    context['token_id']))
        except KeyError:
            # This might happen if we use the Admin token, for instance
            raise exception.ValidationError(
                _('A scoped token must be used'))
        except (exception.TokenNotFound,
                exception.UnsupportedTokenVersionException):
            LOG.warning(_LW('Invalid token found while getting domain ID '
                            'for list request'))
            raise exception.Unauthorized()

        if token_ref.domain_scoped:
            return token_ref.domain_id
        elif token_ref.project_scoped:
            return token_ref.project_domain_id
        else:
            raise exception.ValidationError('Wrong token scope: not'
                    ' domain-scoped nor project-scoped.')
Example #26
0
 def print_warning(self):
     LOG.warning(
         _LW('XML support has been removed as of the Kilo release '
             'and should not be referenced or used in deployment. '
             'Please remove references to XmlBodyMiddleware from '
             'your configuration. This compatibility stub will be '
             'removed in the L release'))
Example #27
0
    def validate_key_repository(self, requires_write=False):
        """Validate permissions on the key repository directory."""
        # NOTE(lbragstad): We shouldn't need to check if the directory was
        # passed in as None because we don't set allow_no_values to True.

        # ensure current user has sufficient access to the key repository
        is_valid = (os.access(self.key_repository, os.R_OK)
                    and os.access(self.key_repository, os.X_OK))
        if requires_write:
            is_valid = (is_valid and os.access(self.key_repository, os.W_OK))

        if not is_valid:
            LOG.error(
                _LE('Either [%(config_group)s] key_repository does not exist '
                    'or Keystone does not have sufficient permission to '
                    'access it: %(key_repo)s'), {
                        'key_repo': self.key_repository,
                        'config_group': self.config_group
                    })
        else:
            # ensure the key repository isn't world-readable
            stat_info = os.stat(self.key_repository)
            if (stat_info.st_mode & stat.S_IROTH
                    or stat_info.st_mode & stat.S_IXOTH):
                LOG.warning(_LW('key_repository is world readable: %s'),
                            self.key_repository)

        return is_valid
Example #28
0
    def _apply_region_proxy(self, proxy_list):
        if isinstance(proxy_list, list):
            proxies = []

            for item in proxy_list:
                if isinstance(item, str):
                    LOG.debug('Importing class %s as KVS proxy.', item)
                    pxy = importutils.import_class(item)
                else:
                    pxy = item

                if issubclass(pxy, proxy.ProxyBackend):
                    proxies.append(pxy)
                else:
                    pxy_cls_name = reflection.get_class_name(
                        pxy, fully_qualified=False)
                    LOG.warning(_LW('%s is not a dogpile.proxy.ProxyBackend'),
                                pxy_cls_name)

            for proxy_cls in reversed(proxies):
                proxy_cls_name = reflection.get_class_name(
                    proxy_cls, fully_qualified=False)
                LOG.info(_LI('Adding proxy \'%(proxy)s\' to KVS %(name)s.'),
                         {'proxy': proxy_cls_name,
                          'name': self._region.name})
                self._region.wrap(proxy_cls)
Example #29
0
    def __call__(self, req):
        arg_dict = req.environ['wsgiorg.routing_args'][1]
        action = arg_dict.pop('action')
        del arg_dict['controller']

        params = req.environ.get(PARAMS_ENV, {})
        params.update(arg_dict)

        # TODO(termie): do some basic normalization on methods
        method = getattr(self, action)

        # NOTE(morganfainberg): use the request method to normalize the
        # response code between GET and HEAD requests. The HTTP status should
        # be the same.
        LOG.info('%(req_method)s %(uri)s', {
            'req_method': req.method.upper(),
            'uri': wsgiref.util.request_uri(req.environ),
        })

        params = self._normalize_dict(params)

        try:
            result = method(req, **params)
        except exception.Unauthorized as e:
            LOG.warning(
                _LW("Authorization failed. %(exception)s from "
                    "%(remote_addr)s"),
                {'exception': e, 'remote_addr': req.environ['REMOTE_ADDR']})
            return render_exception(e,
                                    context=req.context_dict,
                                    user_locale=best_match_language(req))
        except exception.Error as e:
            LOG.warning(six.text_type(e))
            return render_exception(e,
                                    context=req.context_dict,
                                    user_locale=best_match_language(req))
        except TypeError as e:
            LOG.exception(six.text_type(e))
            return render_exception(exception.ValidationError(e),
                                    context=req.context_dict,
                                    user_locale=best_match_language(req))
        except Exception as e:
            LOG.exception(six.text_type(e))
            return render_exception(exception.UnexpectedError(exception=e),
                                    context=req.context_dict,
                                    user_locale=best_match_language(req))

        if result is None:
            return render_response(status=(204, 'No Content'))
        elif isinstance(result, six.string_types):
            return result
        elif isinstance(result, webob.Response):
            return result
        elif isinstance(result, webob.exc.WSGIHTTPException):
            return result

        response_code = self._get_response_code(req)
        return render_response(body=result,
                               status=response_code,
                               method=req.method)
Example #30
0
    def get_token_provider(cls):
        """Return package path to the configured token provider.

        The value should come from ``keystone.conf`` ``[token] provider``,
        however this method ensures backwards compatibility for
        ``keystone.conf`` ``[signing] token_format`` until Havana + 2.

        Return the provider based on ``token_format`` if ``provider`` is not
        set. Otherwise, ignore ``token_format`` and return the configured
        ``provider`` instead.

        """

        if CONF.signing.token_format:
            LOG.warn(
                _LW(
                    "[signing] token_format is deprecated. "
                    "Please change to setting the [token] provider "
                    "configuration value instead"
                )
            )
            try:

                mapped = _FORMAT_TO_PROVIDER[CONF.signing.token_format]
            except KeyError:
                raise exception.UnexpectedError(
                    _("Unrecognized keystone.conf [signing] token_format: " "expected either 'UUID' or 'PKI'")
                )
            return mapped

        if CONF.token.provider is None:
            return UUID_PROVIDER
        else:
            return CONF.token.provider
Example #31
0
    def _get_domain_id_for_list_request(self, request):
        """Get the domain_id for a v3 list call.

        If we running with multiple domain drivers, then the caller must
        specify a domain_id either as a filter or as part of the token scope.

        """
        if not CONF.identity.domain_specific_drivers_enabled:
            # We don't need to specify a domain ID in this case
            return

        domain_id = request.params.get('domain_id')
        if domain_id:
            return domain_id

        token_ref = utils.get_token_ref(request.context_dict)

        if token_ref.domain_scoped:
            return token_ref.domain_id
        elif token_ref.project_scoped:
            return token_ref.project_domain_id
        else:
            LOG.warning(
                _LW('No domain information specified as part of list request'))
            raise exception.Unauthorized()
Example #32
0
        def _get_group_members(ref):
            """Get a list of group members.

            Get the list of group members.  If this fails with
            GroupNotFound, then log this as a warning, but allow
            overall processing to continue.

            """
            try:
                members = self.identity_api.list_users_in_group(
                    ref['group']['id'])
            except exception.GroupNotFound:
                members = []
                # The group is missing, which should not happen since
                # group deletion should remove any related assignments, so
                # log a warning
                target = 'Unknown'
                # Should always be a domain or project, but since to get
                # here things have gone astray, let's be cautious.
                if 'scope' in ref:
                    if 'domain' in ref['scope']:
                        dom_id = ref['scope']['domain'].get('id', 'Unknown')
                        target = 'Domain: %s' % dom_id
                    elif 'project' in ref['scope']:
                        proj_id = ref['scope']['project'].get('id', 'Unknown')
                        target = 'Project: %s' % proj_id
                role_id = 'Unknown'
                if 'role' in ref and 'id' in ref['role']:
                    role_id = ref['role']['id']
                LOG.warning(
                    _LW('Group %(group)s not found for role-assignment - '
                        '%(target)s with Role: %(role)s'), {
                            'group': ref['group']['id'], 'target': target,
                            'role': role_id})
            return members
Example #33
0
    def _apply_region_proxy(self, proxy_list):
        if isinstance(proxy_list, list):
            proxies = []

            for item in proxy_list:
                if isinstance(item, str):
                    LOG.debug('Importing class %s as KVS proxy.', item)
                    pxy = importutils.import_class(item)
                else:
                    pxy = item

                if issubclass(pxy, proxy.ProxyBackend):
                    proxies.append(pxy)
                else:
                    pxy_cls_name = reflection.get_class_name(
                        pxy, fully_qualified=False)
                    LOG.warning(_LW('%s is not a dogpile.proxy.ProxyBackend'),
                                pxy_cls_name)

            for proxy_cls in reversed(proxies):
                proxy_cls_name = reflection.get_class_name(
                    proxy_cls, fully_qualified=False)
                LOG.info(_LI('Adding proxy \'%(proxy)s\' to KVS %(name)s.'), {
                    'proxy': proxy_cls_name,
                    'name': self._region.name
                })
                self._region.wrap(proxy_cls)
Example #34
0
    def process_request(self, request):

        # The request context stores itself in thread-local memory for logging.
        request_context = oslo_context.RequestContext(
            request_id=request.environ.get('openstack.request_id'))

        if authorization.AUTH_CONTEXT_ENV in request.environ:
            msg = _LW('Auth context already exists in the request '
                      'environment; it will be used for authorization '
                      'instead of creating a new one.')
            LOG.warning(msg)
            return

        auth_context, token_id, is_admin = self._build_auth_context(request)

        request_context.auth_token = token_id
        request_context.is_admin = is_admin

        if auth_context is None:
            # The client didn't send any auth info, so don't set auth context.
            return

        # The attributes of request_context are put into the logs. This is a
        # common pattern for all the OpenStack services. In all the other
        # projects these are IDs, so set the attributes to IDs here rather than
        # the name.
        request_context.user = auth_context.get('user_id')
        request_context.tenant = auth_context.get('project_id')
        request_context.domain = auth_context.get('domain_id')
        request_context.user_domain = auth_context.get('user_domain_id')
        request_context.project_domain = auth_context.get('project_domain_id')

        LOG.debug('RBAC: auth_context: %s', auth_context)
        request.environ[authorization.AUTH_CONTEXT_ENV] = auth_context
Example #35
0
    def get_projects_for_token(self, context, **kw):
        """Get valid tenants for token based on token used to authenticate.

        Pulls the token from the context, validates it and gets the valid
        tenants for the user in the token.

        Doesn't care about token scopedness.

        """
        try:
            token_data = self.token_provider_api.validate_token(
                context['token_id'])
            token_ref = token_model.KeystoneToken(token_id=context['token_id'],
                                                  token_data=token_data)
        except exception.NotFound as e:
            LOG.warning(_LW('Authentication failed: %s'), e)
            raise exception.Unauthorized(e)

        tenant_refs = (
            self.assignment_api.list_projects_for_user(token_ref.user_id))
        tenant_refs = [self.v3_to_v2_project(ref) for ref in tenant_refs
                       if ref['domain_id'] == CONF.identity.default_domain_id]
        params = {
            'limit': context['query_string'].get('limit'),
            'marker': context['query_string'].get('marker'),
        }
        return self.format_project_list(tenant_refs, **params)
Example #36
0
    def __init__(self, conf_obj, keystone_user, keystone_group, rebuild,
                 **kwargs):
        self.conf_dir = os.path.dirname(conf_obj.ca_certs)
        self.use_keystone_user = keystone_user
        self.use_keystone_group = keystone_group
        self.rebuild = rebuild
        self.ssl_config_file_name = os.path.join(self.conf_dir, "openssl.conf")
        self.request_file_name = os.path.join(self.conf_dir, "req.pem")
        self.ssl_dictionary = {
            'conf_dir': self.conf_dir,
            'ca_cert': conf_obj.ca_certs,
            'default_md': 'default',
            'ssl_config': self.ssl_config_file_name,
            'ca_private_key': conf_obj.ca_key,
            'request_file': self.request_file_name,
            'signing_key': conf_obj.keyfile,
            'signing_cert': conf_obj.certfile,
            'key_size': int(conf_obj.key_size),
            'valid_days': int(conf_obj.valid_days),
            'cert_subject': conf_obj.cert_subject
        }

        try:
            # OpenSSL 1.0 and newer support default_md = default,
            # older versions do not
            openssl_ver = subprocess.check_output(  # nosec : the arguments
                # are hardcoded and just check the openssl version
                ['openssl', 'version'])
            if b'OpenSSL 0.' in openssl_ver:
                self.ssl_dictionary['default_md'] = 'sha1'
        except subprocess.CalledProcessError:
            LOG.warning(
                _LW('Failed to invoke ``openssl version``, '
                    'assuming is v1.0 or newer'))
        self.ssl_dictionary.update(kwargs)
Example #37
0
    def create_key_directory(self, keystone_user_id=None, keystone_group_id=None):
        """Attempt to create the key directory if it doesn't exist."""
        if not os.access(self.key_repository, os.F_OK):
            LOG.info(_LI("key_repository does not appear to exist; attempting to " "create it"))

            try:
                os.makedirs(self.key_repository, 0o700)
            except OSError:
                LOG.error(
                    _LE(
                        "Failed to create key_repository: either it already "
                        "exists or you don't have sufficient permissions to "
                        "create it"
                    )
                )

            if keystone_user_id and keystone_group_id:
                os.chown(self.key_repository, keystone_user_id, keystone_group_id)
            elif keystone_user_id or keystone_group_id:
                LOG.warning(
                    _LW(
                        "Unable to change the ownership of key_repository without "
                        "a keystone user ID and keystone group ID both being "
                        "provided: %s"
                    )
                    % self.key_repository
                )
Example #38
0
def create_key_directory(keystone_user_id=None, keystone_group_id=None):
    """If the configured key directory does not exist, attempt to create it."""
    if not os.access(CONF.fernet_tokens.key_repository, os.F_OK):
        LOG.info(_LI("[fernet_tokens] key_repository does not appear to exist; " "attempting to create it"))

        try:
            os.makedirs(CONF.fernet_tokens.key_repository, 0o700)
        except OSError:
            LOG.error(
                _LE(
                    "Failed to create [fernet_tokens] key_repository: either it "
                    "already exists or you don't have sufficient permissions to "
                    "create it"
                )
            )

        if keystone_user_id and keystone_group_id:
            os.chown(CONF.fernet_tokens.key_repository, keystone_user_id, keystone_group_id)
        elif keystone_user_id or keystone_group_id:
            LOG.warning(
                _LW(
                    "Unable to change the ownership of [fernet_tokens] "
                    "key_repository without a keystone user ID and keystone group "
                    "ID both being provided: %s"
                )
                % CONF.fernet_tokens.key_repository
            )
Example #39
0
    def validate_key_repository(self, requires_write=False):
        """Validate permissions on the key repository directory."""
        # NOTE(lbragstad): We shouldn't need to check if the directory was
        # passed in as None because we don't set allow_no_values to True.

        # ensure current user has sufficient access to the key repository
        is_valid = os.access(self.key_repository, os.R_OK) and os.access(self.key_repository, os.X_OK)
        if requires_write:
            is_valid = is_valid and os.access(self.key_repository, os.W_OK)

        if not is_valid:
            LOG.error(
                _LE(
                    "Either [fernet_tokens] key_repository does not exist or "
                    "Keystone does not have sufficient permission to access "
                    "it: %s"
                ),
                self.key_repository,
            )
        else:
            # ensure the key repository isn't world-readable
            stat_info = os.stat(self.key_repository)
            if stat_info.st_mode & stat.S_IROTH or stat_info.st_mode & stat.S_IXOTH:
                LOG.warning(_LW("key_repository is world readable: %s"), self.key_repository)

        return is_valid
Example #40
0
    def __init__(self, conf_obj, server_conf_obj, keystone_user,
                 keystone_group, rebuild, **kwargs):
        self.conf_dir = os.path.dirname(server_conf_obj.ca_certs)
        self.use_keystone_user = keystone_user
        self.use_keystone_group = keystone_group
        self.rebuild = rebuild
        self.ssl_config_file_name = os.path.join(self.conf_dir, "openssl.conf")
        self.request_file_name = os.path.join(self.conf_dir, "req.pem")
        self.ssl_dictionary = {'conf_dir': self.conf_dir,
                               'ca_cert': server_conf_obj.ca_certs,
                               'default_md': 'default',
                               'ssl_config': self.ssl_config_file_name,
                               'ca_private_key': conf_obj.ca_key,
                               'request_file': self.request_file_name,
                               'signing_key': server_conf_obj.keyfile,
                               'signing_cert': server_conf_obj.certfile,
                               'key_size': int(conf_obj.key_size),
                               'valid_days': int(conf_obj.valid_days),
                               'cert_subject': conf_obj.cert_subject}

        try:
            # OpenSSL 1.0 and newer support default_md = default,
            # older versions do not
            openssl_ver = environment.subprocess.check_output(
                ['openssl', 'version'])
            if "OpenSSL 0." in openssl_ver:
                self.ssl_dictionary['default_md'] = 'sha1'
        except environment.subprocess.CalledProcessError:
            LOG.warn(_LW('Failed to invoke ``openssl version``, '
                         'assuming is v1.0 or newer'))
        self.ssl_dictionary.update(kwargs)
Example #41
0
def validate_key_repository():
    """Validate permissions on the key repository directory."""
    # NOTE(lbragstad): We shouldn't need to check if the directory was passed
    # in as None because we don't set allow_no_values to True.

    # ensure current user has full access to the key repository
    if (
        not os.access(CONF.fernet_tokens.key_repository, os.R_OK)
        or not os.access(CONF.fernet_tokens.key_repository, os.W_OK)
        or not os.access(CONF.fernet_tokens.key_repository, os.X_OK)
    ):
        LOG.error(
            _LE(
                "Either [fernet_tokens] key_repository does not exist or "
                "Keystone does not have sufficient permission to access it: "
                "%s"
            ),
            CONF.fernet_tokens.key_repository,
        )
        return False

    # ensure the key repository isn't world-readable
    stat_info = os.stat(CONF.fernet_tokens.key_repository)
    if stat_info.st_mode & stat.S_IROTH or stat_info.st_mode & stat.S_IXOTH:
        LOG.warning(_LW("[fernet_tokens] key_repository is world readable: %s"), CONF.fernet_tokens.key_repository)

    return True
Example #42
0
def _build_policy_check_credentials(self, action, context, kwargs):
    kwargs_str = ', '.join(['%s=%s' % (k, kwargs[k]) for k in kwargs])
    kwargs_str = strutils.mask_password(kwargs_str)

    LOG.debug('RBAC: Authorizing %(action)s(%(kwargs)s)', {
        'action': action,
        'kwargs': kwargs_str})

    # see if auth context has already been created. If so use it.
    if ('environment' in context and
            authorization.AUTH_CONTEXT_ENV in context['environment']):
        LOG.debug('RBAC: using auth context from the request environment')
        return context['environment'].get(authorization.AUTH_CONTEXT_ENV)

    # There is no current auth context, build it from the incoming token.
    # TODO(morganfainberg): Collapse this logic with AuthContextMiddleware
    # in a sane manner as this just mirrors the logic in AuthContextMiddleware
    try:
        LOG.debug('RBAC: building auth context from the incoming auth token')
        token_ref = token_model.KeystoneToken(
            token_id=context['token_id'],
            token_data=self.token_provider_api.validate_token(
                context['token_id']))
        # NOTE(jamielennox): whilst this maybe shouldn't be within this
        # function it would otherwise need to reload the token_ref from
        # backing store.
        wsgi.validate_token_bind(context, token_ref)
    except exception.TokenNotFound:
        LOG.warning(_LW('RBAC: Invalid token'))
        raise exception.Unauthorized()

    auth_context = authorization.token_to_auth_context(token_ref)

    return auth_context
Example #43
0
    def _build_auth_context(self, request):
        token_id = request.headers.get(AUTH_TOKEN_HEADER).strip()

        if token_id == CONF.admin_token:
            # NOTE(gyee): no need to proceed any further as the special admin
            # token is being handled by AdminTokenAuthMiddleware. This code
            # will not be impacted even if AdminTokenAuthMiddleware is removed
            # from the pipeline as "is_admin" is default to "False". This code
            # is independent of AdminTokenAuthMiddleware.
            return {}

        context = {'token_id': token_id}
        context['environment'] = request.environ

        try:
            token_ref = token_model.KeystoneToken(
                token_id=token_id,
                token_data=self.token_provider_api.validate_token(token_id))
            # TODO(gyee): validate_token_bind should really be its own
            # middleware
            wsgi.validate_token_bind(context, token_ref)
            return authorization.token_to_auth_context(token_ref)
        except exception.TokenNotFound:
            LOG.warning(_LW('RBAC: Invalid token'))
            raise exception.Unauthorized()
Example #44
0
def create_key_directory(keystone_user_id=None, keystone_group_id=None):
    """If the configured key directory does not exist, attempt to create it."""
    if not os.access(CONF.fernet_tokens.key_repository, os.F_OK):
        LOG.info(_LI(
            '[fernet_tokens] key_repository does not appear to exist; '
            'attempting to create it'))

        try:
            os.makedirs(CONF.fernet_tokens.key_repository, 0o700)
        except OSError:
            LOG.error(_LE(
                'Failed to create [fernet_tokens] key_repository: either it '
                'already exists or you don\'t have sufficient permissions to '
                'create it'))

        if keystone_user_id and keystone_group_id:
            os.chown(
                CONF.fernet_tokens.key_repository,
                keystone_user_id,
                keystone_group_id)
        elif keystone_user_id or keystone_group_id:
            LOG.warning(_LW(
                'Unable to change the ownership of [fernet_tokens] '
                'key_repository without a keystone user ID and keystone group '
                'ID both being provided: %s') %
                CONF.fernet_tokens.key_repository)
Example #45
0
 def __init__(self, application):
     super(AdminTokenAuthMiddleware, self).__init__(application)
     LOG.warning(_LW("The admin_token_auth middleware presents a security "
                     "risk and should be removed from the "
                     "[pipeline:api_v3], [pipeline:admin_api], and "
                     "[pipeline:public_api] sections of your paste ini "
                     "file."))
Example #46
0
def _create_new_key(keystone_user_id, keystone_group_id):
    """Securely create a new encryption key.

    Create a new key that is readable by the Keystone group and Keystone user.
    """
    key = fernet.Fernet.generate_key()  # key is bytes

    # This ensures the key created is not world-readable
    old_umask = os.umask(0o177)
    if keystone_user_id and keystone_group_id:
        old_egid = os.getegid()
        old_euid = os.geteuid()
        os.setegid(keystone_group_id)
        os.seteuid(keystone_user_id)
    elif keystone_user_id or keystone_group_id:
        LOG.warning(_LW(
            'Unable to change the ownership of the new key without a keystone '
            'user ID and keystone group ID both being provided: %s') %
            CONF.fernet_tokens.key_repository)
    # Determine the file name of the new key
    key_file = os.path.join(CONF.fernet_tokens.key_repository, '0')
    try:
        with open(key_file, 'w') as f:
            f.write(key.decode('utf-8'))  # convert key to str for the file.
    finally:
        # After writing the key, set the umask back to it's original value. Do
        # the same with group and user identifiers if a Keystone group or user
        # was supplied.
        os.umask(old_umask)
        if keystone_user_id and keystone_group_id:
            os.seteuid(old_euid)
            os.setegid(old_egid)

    LOG.info(_LI('Created a new key: %s'), key_file)
Example #47
0
def validate_key_repository(requires_write=False):
    """Validate permissions on the key repository directory."""
    # NOTE(lbragstad): We shouldn't need to check if the directory was passed
    # in as None because we don't set allow_no_values to True.

    # ensure current user has sufficient access to the key repository
    is_valid = (os.access(CONF.fernet_tokens.key_repository, os.R_OK) and
                os.access(CONF.fernet_tokens.key_repository, os.X_OK))
    if requires_write:
        is_valid = (is_valid and
                    os.access(CONF.fernet_tokens.key_repository, os.W_OK))

    if not is_valid:
        LOG.error(
            _LE('Either [fernet_tokens] key_repository does not exist or '
                'Keystone does not have sufficient permission to access it: '
                '%s'), CONF.fernet_tokens.key_repository)
    else:
        # ensure the key repository isn't world-readable
        stat_info = os.stat(CONF.fernet_tokens.key_repository)
        if(stat_info.st_mode & stat.S_IROTH or
           stat_info.st_mode & stat.S_IXOTH):
            LOG.warning(_LW(
                '[fernet_tokens] key_repository is world readable: %s'),
                CONF.fernet_tokens.key_repository)

    return is_valid
Example #48
0
        def wrapper(self, context, **kwargs):
            if not context['is_admin']:
                action = 'identity:%s' % f.__name__
                creds = _build_policy_check_credentials(self, action,
                                                        context, kwargs)
                # Now, build the target dict for policy check.  We include:
                #
                # - Any query filter parameters
                # - Data from the main url (which will be in the kwargs
                #   parameter) and would typically include the prime key
                #   of a get/update/delete call
                #
                # First  any query filter parameters
                target = dict()
                if filters:
                    for item in filters:
                        if item in context['query_string']:
                            target[item] = context['query_string'][item]

                    LOG.debug('RBAC: Adding query filter params (%s)', (
                        ', '.join(['%s=%s' % (item, target[item])
                                  for item in target])))

                # Now any formal url parameters
                for key in kwargs:
                    target[key] = kwargs[key]

                self.policy_api.enforce(creds,
                                        action,
                                        utils.flatten_dict(target))

                LOG.debug('RBAC: Authorization granted')
            else:
                LOG.warning(_LW('RBAC: Bypassing authorization'))
            return f(self, context, filters, **kwargs)
Example #49
0
 def main(cls):
     LOG.warn(_LW('keystone-manage ssl_setup is not recommended for '
                  'production use.'))
     keystone_user_id, keystone_group_id = cls.get_user_group()
     conf_ssl = openssl.ConfigureSSL(keystone_user_id, keystone_group_id,
                                     rebuild=CONF.command.rebuild)
     conf_ssl.run()
Example #50
0
    def get_projects_for_token(self, context, **kw):
        """Get valid tenants for token based on token used to authenticate.

        Pulls the token from the context, validates it and gets the valid
        tenants for the user in the token.

        Doesn't care about token scopedness.

        """
        try:
            token_data = self.token_provider_api.validate_token(
                context['token_id'])
            token_ref = token_model.KeystoneToken(token_id=context['token_id'],
                                                  token_data=token_data)
        except exception.NotFound as e:
            LOG.warning(_LW('Authentication failed: %s'), e)
            raise exception.Unauthorized(e)

        tenant_refs = (self.assignment_api.list_projects_for_user(
            token_ref.user_id))
        tenant_refs = [
            self.filter_domain_id(ref) for ref in tenant_refs
            if ref['domain_id'] == CONF.identity.default_domain_id
        ]
        params = {
            'limit': context['query_string'].get('limit'),
            'marker': context['query_string'].get('marker'),
        }
        return self.format_project_list(tenant_refs, **params)
Example #51
0
    def _get_domain_id_for_list_request(self, request):
        """Get the domain_id for a v3 list call.

        If we running with multiple domain drivers, then the caller must
        specify a domain_id either as a filter or as part of the token scope.

        """
        if not CONF.identity.domain_specific_drivers_enabled:
            # We don't need to specify a domain ID in this case
            return

        domain_id = request.params.get('domain_id')
        if domain_id:
            return domain_id

        token_ref = utils.get_token_ref(request.context_dict)

        if token_ref.domain_scoped:
            return token_ref.domain_id
        elif token_ref.project_scoped:
            return token_ref.project_domain_id
        else:
            LOG.warning(
                _LW('No domain information specified as part of list request'))
            raise exception.Unauthorized()
Example #52
0
    def _build_auth_context(self, request):
        token_id = request.headers.get(AUTH_TOKEN_HEADER).strip()

        if token_id == CONF.admin_token:
            # NOTE(gyee): no need to proceed any further as the special admin
            # token is being handled by AdminTokenAuthMiddleware. This code
            # will not be impacted even if AdminTokenAuthMiddleware is removed
            # from the pipeline as "is_admin" is default to "False". This code
            # is independent of AdminTokenAuthMiddleware.
            return {}

        context = {'token_id': token_id}
        context['environment'] = request.environ

        try:
            token_ref = token_model.KeystoneToken(
                token_id=token_id,
                token_data=self.token_provider_api.validate_token(token_id))
            # TODO(gyee): validate_token_bind should really be its own
            # middleware
            wsgi.validate_token_bind(context, token_ref)
            return authorization.token_to_auth_context(token_ref)
        except exception.TokenNotFound:
            LOG.warning(_LW('RBAC: Invalid token'))
            raise exception.Unauthorized()
Example #53
0
    def check_protection(self, request, prep_info, target_attr=None):
        """Provide call protection for complex target attributes.

        As well as including the standard parameters from the original API
        call (which is passed in prep_info), this call will add in any
        additional entities or attributes (passed in target_attr), so that
        they can be referenced by policy rules.

        """
        if request.context.is_admin:
            LOG.warning(_LW('RBAC: Bypassing authorization'))
        else:
            action = 'identity:%s' % prep_info['f_name']
            # TODO(henry-nash) need to log the target attributes as well
            creds = _build_policy_check_credentials(self, action,
                                                    request.context_dict,
                                                    prep_info['input_attr'])
            # Build the dict the policy engine will check against from both the
            # parameters passed into the call we are protecting (which was
            # stored in the prep_info by protected()), plus the target
            # attributes provided.
            policy_dict = {}
            if target_attr:
                policy_dict = {'target': target_attr}
            policy_dict.update(prep_info['input_attr'])
            if 'filter_attr' in prep_info:
                policy_dict.update(prep_info['filter_attr'])
            self.policy_api.enforce(creds, action,
                                    utils.flatten_dict(policy_dict))
            LOG.debug('RBAC: Authorization granted')
Example #54
0
def checkout_vendor(repo, rev):
    # TODO(termie): this function is a good target for some optimizations :PERF
    name = repo.split('/')[-1]
    if name.endswith('.git'):
        name = name[:-4]

    working_dir = os.getcwd()
    revdir = os.path.join(VENDOR, '%s-%s' % (name, rev.replace('/', '_')))
    modcheck = os.path.join(VENDOR, '.%s-%s' % (name, rev.replace('/', '_')))
    try:
        if os.path.exists(modcheck):
            mtime = os.stat(modcheck).st_mtime
            if int(time.time()) - mtime < 10000:
                return revdir

        if not os.path.exists(revdir):
            utils.git('clone', repo, revdir)

        os.chdir(revdir)
        utils.git('checkout', '-q', 'master')
        utils.git('pull', '-q')
        utils.git('checkout', '-q', rev)

        # write out a modified time
        with open(modcheck, 'w') as fd:
            fd.write('1')
    except environment.subprocess.CalledProcessError:
        LOG.warning(_LW('Failed to checkout %s'), repo)
    os.chdir(working_dir)
    return revdir
Example #55
0
    def check_protection(self, context, prep_info, target_attr=None):
        """Provide call protection for complex target attributes.

        As well as including the standard parameters from the original API
        call (which is passed in prep_info), this call will add in any
        additional entities or attributes (passed in target_attr), so that
        they can be referenced by policy rules.

        """
        if 'is_admin' in context and context['is_admin']:
            LOG.warning(_LW('RBAC: Bypassing authorization'))
        else:
            action = 'identity:%s' % prep_info['f_name']
            # TODO(henry-nash) need to log the target attributes as well
            creds = _build_policy_check_credentials(self, action,
                                                    context,
                                                    prep_info['input_attr'])
            # Build the dict the policy engine will check against from both the
            # parameters passed into the call we are protecting (which was
            # stored in the prep_info by protected()), plus the target
            # attributes provided.
            policy_dict = {}
            if target_attr:
                policy_dict = {'target': target_attr}
            policy_dict.update(prep_info['input_attr'])
            if 'filter_attr' in prep_info:
                policy_dict.update(prep_info['filter_attr'])
            self.policy_api.enforce(creds,
                                    action,
                                    utils.flatten_dict(policy_dict))
            LOG.debug('RBAC: Authorization granted')
Example #56
0
def _build_policy_check_credentials(self, action, context, kwargs):
    kwargs_str = ', '.join(['%s=%s' % (k, kwargs[k]) for k in kwargs])
    kwargs_str = strutils.mask_password(kwargs_str)

    LOG.debug('RBAC: Authorizing %(action)s(%(kwargs)s)', {
        'action': action,
        'kwargs': kwargs_str
    })

    # see if auth context has already been created. If so use it.
    if ('environment' in context
            and authorization.AUTH_CONTEXT_ENV in context['environment']):
        LOG.debug('RBAC: using auth context from the request environment')
        return context['environment'].get(authorization.AUTH_CONTEXT_ENV)

    # There is no current auth context, build it from the incoming token.
    # TODO(morganfainberg): Collapse this logic with AuthContextMiddleware
    # in a sane manner as this just mirrors the logic in AuthContextMiddleware
    try:
        LOG.debug('RBAC: building auth context from the incoming auth token')
        token_ref = token_model.KeystoneToken(
            token_id=context['token_id'],
            token_data=self.token_provider_api.validate_token(
                context['token_id']))
        # NOTE(jamielennox): whilst this maybe shouldn't be within this
        # function it would otherwise need to reload the token_ref from
        # backing store.
        wsgi.validate_token_bind(context, token_ref)
    except exception.TokenNotFound:
        LOG.warning(_LW('RBAC: Invalid token'))
        raise exception.Unauthorized()

    auth_context = authorization.token_to_auth_context(token_ref)

    return auth_context
Example #57
0
    def get_token_provider(cls):
        """Return package path to the configured token provider.

        The value should come from ``keystone.conf`` ``[token] provider``,
        however this method ensures backwards compatibility for
        ``keystone.conf`` ``[signing] token_format`` until Havana + 2.

        Return the provider based on ``token_format`` if ``provider`` is not
        set. Otherwise, ignore ``token_format`` and return the configured
        ``provider`` instead.

        """

        if CONF.signing.token_format:
            LOG.warn(
                _LW('[signing] token_format is deprecated. '
                    'Please change to setting the [token] provider '
                    'configuration value instead'))
            try:

                mapped = _FORMAT_TO_PROVIDER[CONF.signing.token_format]
            except KeyError:
                raise exception.UnexpectedError(
                    _('Unrecognized keystone.conf [signing] token_format: '
                      'expected either \'UUID\' or \'PKI\''))
            return mapped

        if CONF.token.provider is None:
            return UUID_PROVIDER
        else:
            return CONF.token.provider
Example #58
0
        def inner(self, request, *args, **kwargs):
            request.assert_authenticated()

            if request.context.is_admin:
                LOG.warning(_LW('RBAC: Bypassing authorization'))
            elif callback is not None:
                prep_info = {'f_name': f.__name__, 'input_attr': kwargs}
                callback(self, request, prep_info, *args, **kwargs)
            else:
                action = 'identity:%s' % f.__name__
                creds = _build_policy_check_credentials(
                    self, action, request.context_dict, kwargs)

                policy_dict = {}

                # Check to see if we need to include the target entity in our
                # policy checks.  We deduce this by seeing if the class has
                # specified a get_member() method and that kwargs contains the
                # appropriate entity id.
                if (hasattr(self, 'get_member_from_driver')
                        and self.get_member_from_driver is not None):
                    key = '%s_id' % self.member_name
                    if key in kwargs:
                        ref = self.get_member_from_driver(kwargs[key])
                        policy_dict['target'] = {self.member_name: ref}

                # TODO(henry-nash): Move this entire code to a member
                # method inside v3 Auth
                if request.context_dict.get('subject_token_id') is not None:
                    window_seconds = self._token_validation_window(request)
                    token_ref = token_model.KeystoneToken(
                        token_id=request.context_dict['subject_token_id'],
                        token_data=self.token_provider_api.validate_token(
                            request.context_dict['subject_token_id'],
                            window_seconds=window_seconds))
                    policy_dict.setdefault('target', {})
                    policy_dict['target'].setdefault(self.member_name, {})
                    policy_dict['target'][self.member_name]['user_id'] = (
                        token_ref.user_id)
                    try:
                        user_domain_id = token_ref.user_domain_id
                    except exception.UnexpectedError:
                        user_domain_id = None
                    if user_domain_id:
                        policy_dict['target'][self.member_name].setdefault(
                            'user', {})
                        policy_dict['target'][
                            self.member_name]['user'].setdefault('domain', {})
                        policy_dict['target'][
                            self.member_name]['user']['domain']['id'] = (
                                user_domain_id)

                # Add in the kwargs, which means that any entity provided as a
                # parameter for calls like create and update will be included.
                policy_dict.update(kwargs)
                self.policy_api.enforce(creds, action,
                                        utils.flatten_dict(policy_dict))
                LOG.debug('RBAC: Authorization granted')
            return f(self, request, *args, **kwargs)