Example #1
0
    def verify_token(self, user_token, retry=True):
        """Authenticate user token with identity server.

        :param user_token: user's token id
        :param retry: flag that forces the middleware to retry
                      user authentication when an indeterminate
                      response is received. Optional.
        :returns: access info received from identity server on success
        :rtype: :py:class:`keystoneauth1.access.AccessInfo`
        :raises exc.InvalidToken: if token is rejected
        :raises exc.ServiceError: if unable to authenticate token

        """
        try:
            auth_ref = self._request_strategy.verify_token(user_token)
        except ksa_exceptions.NotFound as e:
            self._LOG.warning(_LW("Authorization failed for token"))
            self._LOG.warning(_LW("Identity response: %s"), e.response.text)
            raise ksm_exceptions.InvalidToken(_("Token authorization failed"))
        except ksa_exceptions.Unauthorized as e:
            self._LOG.info(_LI("Identity server rejected authorization"))
            self._LOG.warning(_LW("Identity response: %s"), e.response.text)
            if retry:
                self._LOG.info(_LI("Retrying validation"))
                return self.verify_token(user_token, False)
            msg = _("Identity server rejected authorization necessary to " "fetch token data")
            raise ksm_exceptions.ServiceError(msg)
        except ksa_exceptions.HttpError as e:
            self._LOG.error(_LE("Bad response code while validating token: %s"), e.http_status)
            self._LOG.warning(_LW("Identity response: %s"), e.response.text)
            msg = _("Failed to fetch token data from identity server")
            raise ksm_exceptions.ServiceError(msg)
        else:
            return auth_ref
    def verify_token(self, user_token, retry=True):
        """Authenticate user token with identity server.

        :param user_token: user's token id
        :param retry: flag that forces the middleware to retry
                      user authentication when an indeterminate
                      response is received. Optional.
        :returns: token object received from identity server on success
        :raises exc.InvalidToken: if token is rejected
        :raises exc.ServiceError: if unable to authenticate token

        """
        user_token = _utils.safe_quote(user_token)

        try:
            response, data = self._request_strategy.verify_token(user_token)
        except exceptions.NotFound as e:
            self._LOG.warn(_LW('Authorization failed for token'))
            self._LOG.warn(_LW('Identity response: %s'), e.response.text)
        except exceptions.Unauthorized as e:
            self._LOG.info(_LI('Identity server rejected authorization'))
            self._LOG.warn(_LW('Identity response: %s'), e.response.text)
            if retry:
                self._LOG.info(_LI('Retrying validation'))
                return self.verify_token(user_token, False)
        except exceptions.HttpError as e:
            self._LOG.error(
                _LE('Bad response code while validating token: %s'),
                e.http_status)
            self._LOG.warn(_LW('Identity response: %s'), e.response.text)
        else:
            if response.status_code == 200:
                return data

            raise exc.InvalidToken()
    def verify_token(self, user_token, retry=True):
        """Authenticate user token with identity server.

        :param user_token: user's token id
        :param retry: flag that forces the middleware to retry
                      user authentication when an indeterminate
                      response is received. Optional.
        :returns: token object received from identity server on success
        :raises exc.InvalidToken: if token is rejected
        :raises exc.ServiceError: if unable to authenticate token

        """
        user_token = _utils.safe_quote(user_token)

        try:
            response, data = self._request_strategy.verify_token(user_token)
        except exceptions.NotFound as e:
            self._LOG.warn(_LW('Authorization failed for token'))
            self._LOG.warn(_LW('Identity response: %s'), e.response.text)
        except exceptions.Unauthorized as e:
            self._LOG.info(_LI('Identity server rejected authorization'))
            self._LOG.warn(_LW('Identity response: %s'), e.response.text)
            if retry:
                self._LOG.info(_LI('Retrying validation'))
                return self.verify_token(user_token, False)
        except exceptions.HttpError as e:
            self._LOG.error(
                _LE('Bad response code while validating token: %s'),
                e.http_status)
            self._LOG.warn(_LW('Identity response: %s'), e.response.text)
        else:
            if response.status_code == 200:
                return data

            raise exc.InvalidToken()
    def _validate_offline(self, token, token_hashes):
        if cms.is_pkiz(token):
            token_data = _uncompress_pkiz(token)
            inform = cms.PKIZ_CMS_FORM
        elif cms.is_asn1_token(token):
            token_data = cms.token_to_cms(token)
            inform = cms.PKI_ASN1_FORM
        else:
            # Can't do offline validation for this type of token.
            return

        try:
            self._revocations.check(token_hashes)
            verified = self._cms_verify(token_data, inform)
        except ksc_exceptions.CertificateConfigError:
            self.log.warning(_LW('Fetch certificate config failed, '
                                 'fallback to online validation.'))
        except ksm_exceptions.RevocationListError:
            self.log.warning(_LW('Fetch revocation list failed, '
                                 'fallback to online validation.'))
        else:
            data = jsonutils.loads(verified)

            audit_ids = None
            if 'access' in data:
                # It's a v2 token.
                audit_ids = data['access']['token'].get('audit_ids')
            else:
                # It's a v3 token
                audit_ids = data['token'].get('audit_ids')

            if audit_ids:
                self._revocations.check_by_audit_id(audit_ids)

            return data
Example #5
0
    def _validate_offline(self, token, token_hashes):
        try:
            if cms.is_pkiz(token):
                verified = self._verify_pkiz_token(token, token_hashes)
            elif cms.is_asn1_token(token):
                verified = self._verify_signed_token(token, token_hashes)
            else:
                # Can't do offline validation for this type of token.
                return
        except ksc_exceptions.CertificateConfigError:
            self.log.warning(_LW('Fetch certificate config failed, '
                                 'fallback to online validation.'))
        except ksm_exceptions.RevocationListError:
            self.log.warning(_LW('Fetch revocation list failed, '
                                 'fallback to online validation.'))
        else:
            data = jsonutils.loads(verified)

            audit_ids = None
            if 'access' in data:
                # It's a v2 token.
                audit_ids = data['access']['token'].get('audit_ids')
            else:
                # It's a v3 token
                audit_ids = data['token'].get('audit_ids')

            if audit_ids:
                self._revocations.check_by_audit_id(audit_ids)

            return data
Example #6
0
    def _validate_offline(self, token, token_hashes):
        try:
            if cms.is_pkiz(token):
                verified = self._verify_pkiz_token(token, token_hashes)
            elif cms.is_asn1_token(token):
                verified = self._verify_signed_token(token, token_hashes)
            else:
                # Can't do offline validation for this type of token.
                return
        except exceptions.CertificateConfigError:
            self.log.warning(
                _LW('Fetch certificate config failed, '
                    'fallback to online validation.'))
        except exc.RevocationListError:
            self.log.warning(
                _LW('Fetch revocation list failed, '
                    'fallback to online validation.'))
        else:
            data = jsonutils.loads(verified)

            audit_ids = None
            if 'access' in data:
                # It's a v2 token.
                audit_ids = data['access']['token'].get('audit_ids')
            else:
                # It's a v3 token
                audit_ids = data['token'].get('audit_ids')

            if audit_ids:
                self._revocations.check_by_audit_id(audit_ids)

            return data
Example #7
0
    def __init__(self, auth_host, auth_port, auth_protocol, auth_admin_prefix,
                 admin_user, admin_password, admin_tenant_name, admin_token,
                 identity_uri, log):

        log.warning(
            _LW("Use of the auth_admin_prefix, auth_host, auth_port, "
                "auth_protocol, identity_uri, admin_token, admin_user, "
                "admin_password, and admin_tenant_name configuration options was "
                "deprecated in the Mitaka release in favor of an auth_plugin and "
                "its related options. This class may be removed in a future "
                "release."))

        # NOTE(jamielennox): it does appear here that our default arguments
        # are backwards. We need to do it this way so that we can handle the
        # same deprecation strategy for CONF and the conf variable.
        if not identity_uri:
            log.warning(
                _LW('Configuring admin URI using auth fragments was '
                    'deprecated in the Kilo release, and will be '
                    'removed in the Newton release, '
                    'use \'identity_uri\ instead.'))

            if ':' in auth_host:
                # Note(dzyu) it is an IPv6 address, so it needs to be wrapped
                # with '[]' to generate a valid IPv6 URL, based on
                # http://www.ietf.org/rfc/rfc2732.txt
                auth_host = '[%s]' % auth_host

            identity_uri = '%s://%s:%s' % (auth_protocol, auth_host, auth_port)

            if auth_admin_prefix:
                identity_uri = '%s/%s' % (identity_uri,
                                          auth_admin_prefix.strip('/'))

        self._identity_uri = identity_uri.rstrip('/')

        # FIXME(jamielennox): Yes. This is wrong. We should be determining the
        # plugin to use based on a combination of discovery and inputs. Much
        # of this can be changed when we get keystoneclient 0.10. For now this
        # hardcoded path is EXACTLY the same as the original auth_token did.
        auth_url = '%s/v2.0' % self._identity_uri

        if admin_token:
            log.warning(
                _LW("The admin_token option in auth_token middleware was "
                    "deprecated in the Kilo release, and will be removed in the "
                    "Newton release, use admin_user and admin_password instead."
                    ))
            self._plugin = token_endpoint.Token(auth_url, admin_token)
        else:
            self._plugin = v2.Password(auth_url,
                                       username=admin_user,
                                       password=admin_password,
                                       tenant_name=admin_tenant_name)

        self._LOG = log
        self._discover = None
Example #8
0
    def __init__(self, auth_host, auth_port, auth_protocol, auth_admin_prefix,
                 admin_user, admin_password, admin_tenant_name, admin_token,
                 identity_uri, log):

        log.warning(_LW(
            "Use of the auth_admin_prefix, auth_host, auth_port, "
            "auth_protocol, identity_uri, admin_token, admin_user, "
            "admin_password, and admin_tenant_name configuration options was "
            "deprecated in the Mitaka release in favor of an auth_plugin and "
            "its related options. This class may be removed in a future "
            "release."))

        # NOTE(jamielennox): it does appear here that our default arguments
        # are backwards. We need to do it this way so that we can handle the
        # same deprecation strategy for CONF and the conf variable.
        if not identity_uri:
            log.warning(_LW('Configuring admin URI using auth fragments was '
                            'deprecated in the Kilo release, and will be '
                            'removed in the N release, use \'identity_uri\ '
                            'instead.'))

            if ':' in auth_host:
                # Note(dzyu) it is an IPv6 address, so it needs to be wrapped
                # with '[]' to generate a valid IPv6 URL, based on
                # http://www.ietf.org/rfc/rfc2732.txt
                auth_host = '[%s]' % auth_host

            identity_uri = '%s://%s:%s' % (auth_protocol,
                                           auth_host,
                                           auth_port)

            if auth_admin_prefix:
                identity_uri = '%s/%s' % (identity_uri,
                                          auth_admin_prefix.strip('/'))

        self._identity_uri = identity_uri.rstrip('/')

        # FIXME(jamielennox): Yes. This is wrong. We should be determining the
        # plugin to use based on a combination of discovery and inputs. Much
        # of this can be changed when we get keystoneclient 0.10. For now this
        # hardcoded path is EXACTLY the same as the original auth_token did.
        auth_url = '%s/v2.0' % self._identity_uri

        if admin_token:
            log.warning(_LW(
                "The admin_token option in auth_token middleware was "
                "deprecated in the Kilo release, and will be removed in the N "
                "release, use admin_user and admin_password instead."))
            self._plugin = token_endpoint.Token(auth_url, admin_token)
        else:
            self._plugin = v2.Password(auth_url,
                                       username=admin_user,
                                       password=admin_password,
                                       tenant_name=admin_tenant_name)

        self._LOG = log
        self._discover = None
Example #9
0
    def __init__(self, app, conf):
        """Common initialization code."""
        self._app = app
        self._logger = logging.getLogger(conf.get('log_name', __name__))
        self._logger.debug('Starting the %s component', PROTOCOL_NAME)
        self._reseller_prefix = conf.get('reseller_prefix', 'AUTH_')
        # where to find the auth service (we use this to validate tokens)

        self._request_uri = conf.get('auth_uri')
        if not self._request_uri:
            self._logger.warning(
                _LW("Use of the auth_host, auth_port, and auth_protocol "
                    "configuration options was deprecated in the Newton release "
                    "in favor of auth_uri. These options may be removed in a "
                    "future release."))
            auth_host = conf.get('auth_host')
            auth_port = int(conf.get('auth_port', 35357))
            auth_protocol = conf.get('auth_protocol', 'https')

            self._request_uri = '%s://%s:%s' % (auth_protocol, auth_host,
                                                auth_port)

        # SSL
        insecure = strutils.bool_from_string(conf.get('insecure', False))
        cert_file = conf.get('certfile')
        key_file = conf.get('keyfile')

        if insecure:
            self._verify = False
        elif cert_file and key_file:
            self._verify = (cert_file, key_file)
        elif cert_file:
            self._verify = cert_file
        else:
            self._verify = None
Example #10
0
    def __init__(self, app, conf):
        """Common initialization code."""
        self._app = app
        self._logger = logging.getLogger(conf.get('log_name', __name__))
        self._logger.debug('Starting the %s component', PROTOCOL_NAME)
        self._reseller_prefix = conf.get('reseller_prefix', 'AUTH_')
        # where to find the auth service (we use this to validate tokens)

        self._request_uri = conf.get('auth_uri')
        if not self._request_uri:
            self._logger.warning(_LW(
                "Use of the auth_host, auth_port, and auth_protocol "
                "configuration options was deprecated in the Newton release "
                "in favor of auth_uri. These options may be removed in a "
                "future release."))
            auth_host = conf.get('auth_host')
            auth_port = int(conf.get('auth_port', 35357))
            auth_protocol = conf.get('auth_protocol', 'https')

            self._request_uri = '%s://%s:%s' % (auth_protocol, auth_host,
                                                auth_port)

        # SSL
        insecure = strutils.bool_from_string(conf.get('insecure', False))
        cert_file = conf.get('certfile')
        key_file = conf.get('keyfile')

        if insecure:
            self._verify = False
        elif cert_file and key_file:
            self._verify = (cert_file, key_file)
        elif cert_file:
            self._verify = cert_file
        else:
            self._verify = None
 def _verify_signing_dir(self):
     if os.path.isdir(self._directory_name):
         if not os.access(self._directory_name, os.W_OK):
             raise exc.ConfigurationError(
                 _('unable to access signing_dir %s') %
                 self._directory_name)
         uid = os.getuid()
         if os.stat(self._directory_name).st_uid != uid:
             self._log.warning(_LW('signing_dir is not owned by %s'), uid)
         current_mode = stat.S_IMODE(os.stat(self._directory_name).st_mode)
         if current_mode != stat.S_IRWXU:
             self._log.warning(
                 _LW('signing_dir mode is %(mode)s instead of %(need)s'),
                 {'mode': oct(current_mode), 'need': oct(stat.S_IRWXU)})
     else:
         os.makedirs(self._directory_name, stat.S_IRWXU)
Example #12
0
 def _validate_offline(self, token, token_hashes):
     try:
         if cms.is_pkiz(token):
             verified = self._verify_pkiz_token(token, token_hashes)
         elif cms.is_asn1_token(token):
             verified = self._verify_signed_token(token, token_hashes)
         else:
             # Can't do offline validation for this type of token.
             return
     except exceptions.CertificateConfigError:
         self.log.warning(_LW("Fetch certificate config failed, " "fallback to online validation."))
     except exc.RevocationListError:
         self.log.warning(_LW("Fetch revocation list failed, " "fallback to online validation."))
     else:
         data = jsonutils.loads(verified)
         return data
Example #13
0
 def _verify_signing_dir(self):
     if os.path.isdir(self._directory_name):
         if not os.access(self._directory_name, os.W_OK):
             raise exc.ConfigurationError(
                 _('unable to access signing_dir %s') %
                 self._directory_name)
         uid = os.getuid()
         if os.stat(self._directory_name).st_uid != uid:
             self._log.warning(_LW('signing_dir is not owned by %s'), uid)
         current_mode = stat.S_IMODE(os.stat(self._directory_name).st_mode)
         if current_mode != stat.S_IRWXU:
             self._log.warning(
                 _LW('signing_dir mode is %(mode)s instead of %(need)s'),
                 {'mode': oct(current_mode), 'need': oct(stat.S_IRWXU)})
     else:
         os.makedirs(self._directory_name, stat.S_IRWXU)
Example #14
0
 def verify():
     try:
         signing_cert_path = self._signing_directory.calc_path(self._SIGNING_CERT_FILE_NAME)
         signing_ca_path = self._signing_directory.calc_path(self._SIGNING_CA_FILE_NAME)
         return cms.cms_verify(data, signing_cert_path, signing_ca_path, inform=inform).decode("utf-8")
     except cms.subprocess.CalledProcessError as err:
         self.log.warning(_LW("Verify error: %s"), err)
         raise
Example #15
0
    def _do_fetch_token(self, token):
        """Helper method to fetch a token and convert it into an AccessInfo"""
        data = self._fetch_token(token)

        try:
            return data, access.AccessInfo.factory(body=data, auth_token=token)
        except Exception:
            self.log.warning(_LW("Invalid token contents."), exc_info=True)
            raise exc.InvalidToken(_("Token authorization failed"))
    def _do_fetch_token(self, token):
        """Helper method to fetch a token and convert it into an AccessInfo"""
        data = self.fetch_token(token)

        try:
            return data, access.create(body=data, auth_token=token)
        except Exception:
            self.log.warning(_LW('Invalid token contents.'), exc_info=True)
            raise ksm_exceptions.InvalidToken(_('Token authorization failed'))
Example #17
0
    def _do_fetch_token(self, token):
        """Helper method to fetch a token and convert it into an AccessInfo"""
        data = self._fetch_token(token)

        try:
            return data, access.AccessInfo.factory(body=data, auth_token=token)
        except Exception:
            self.log.warning(_LW('Invalid token contents.'), exc_info=True)
            raise exc.InvalidToken(_('Token authorization failed'))
Example #18
0
    def _fetch_token(self, token):
        """Retrieve a token from either a PKI bundle or the identity server.

        :param str token: token id

        :raises exc.InvalidToken: if token is rejected
        """
        data = None
        token_hashes = None

        try:
            token_hashes = self._token_hashes(token)
            cached = self._cache_get_hashes(token_hashes)

            if cached:
                data = cached

                if self._check_revocations_for_cached:
                    # A token stored in Memcached might have been revoked
                    # regardless of initial mechanism used to validate it,
                    # and needs to be checked.
                    self._revocations.check(token_hashes)
            else:
                data = self._validate_offline(token, token_hashes)
                if not data:
                    data = self._identity_server.verify_token(token)

                self._token_cache.store(token_hashes[0], data)

        except (exceptions.ConnectionRefused, exceptions.RequestTimeout):
            self.log.debug('Token validation failure.', exc_info=True)
            self.log.warning(_LW('Authorization failed for token'))
            raise exc.InvalidToken(_('Token authorization failed'))
        except exc.ServiceError as e:
            self.log.critical(_LC('Unable to obtain admin token: %s'), e)
            raise webob.exc.HTTPServiceUnavailable()
        except Exception:
            self.log.debug('Token validation failure.', exc_info=True)
            if token_hashes:
                self._token_cache.store_invalid(token_hashes[0])
            self.log.warning(_LW('Authorization failed for token'))
            raise exc.InvalidToken(_('Token authorization failed'))

        return data
Example #19
0
    def _fetch_token(self, token):
        """Retrieve a token from either a PKI bundle or the identity server.

        :param str token: token id

        :raises exc.InvalidToken: if token is rejected
        """
        data = None
        token_hashes = None

        try:
            token_hashes = self._token_hashes(token)
            cached = self._cache_get_hashes(token_hashes)

            if cached:
                data = cached

                if self._check_revocations_for_cached:
                    # A token stored in Memcached might have been revoked
                    # regardless of initial mechanism used to validate it,
                    # and needs to be checked.
                    self._revocations.check(token_hashes)
            else:
                data = self._validate_offline(token, token_hashes)
                if not data:
                    data = self._identity_server.verify_token(token)

                self._token_cache.store(token_hashes[0], data)

        except (exceptions.ConnectionRefused, exceptions.RequestTimeout):
            self.log.debug("Token validation failure.", exc_info=True)
            self.log.warning(_LW("Authorization failed for token"))
            raise exc.InvalidToken(_("Token authorization failed"))
        except exc.ServiceError as e:
            self.log.critical(_LC("Unable to obtain admin token: %s"), e)
            raise webob.exc.HTTPServiceUnavailable()
        except Exception:
            self.log.debug("Token validation failure.", exc_info=True)
            if token_hashes:
                self._token_cache.store_invalid(token_hashes[0])
            self.log.warning(_LW("Authorization failed for token"))
            raise exc.InvalidToken(_("Token authorization failed"))

        return data
Example #20
0
    def fetch_token(self, token, allow_expired=False):
        """Retrieve a token from either a PKI bundle or the identity server.

        :param str token: token id

        :raises exc.InvalidToken: if token is rejected
        """
        data = None
        token_hashes = None

        try:
            token_hashes = self._token_hashes(token)
            cached = self._cache_get_hashes(token_hashes)

            if cached:
                if cached == _CACHE_INVALID_INDICATOR:
                    self.log.debug('Cached token is marked unauthorized')
                    raise ksm_exceptions.InvalidToken()

                if self._check_revocations_for_cached:
                    # A token might have been revoked, regardless of initial
                    # mechanism used to validate it, and needs to be checked.
                    self._revocations.check(token_hashes)

                # NOTE(jamielennox): Cached values used to be stored as a tuple
                # of data and expiry time. They no longer are but we have to
                # allow some time to transition the old format so if it's a
                # tuple just use the data.
                if len(cached) == 2:
                    cached = cached[0]

                data = cached
            else:
                data = self._validate_offline(token, token_hashes)
                if not data:
                    data = self._identity_server.verify_token(
                        token, allow_expired=allow_expired)

                self._token_cache.set(token_hashes[0], data)

        except (ksa_exceptions.ConnectFailure, ksa_exceptions.RequestTimeout,
                ksm_exceptions.RevocationListError,
                ksm_exceptions.ServiceError) as e:
            self.log.critical(_LC('Unable to validate token: %s'), e)
            raise webob.exc.HTTPServiceUnavailable()
        except ksm_exceptions.InvalidToken:
            self.log.debug('Token validation failure.', exc_info=True)
            if token_hashes:
                self._token_cache.set(token_hashes[0],
                                      _CACHE_INVALID_INDICATOR)
            self.log.warning(_LW('Authorization failed for token'))
            raise

        return data
Example #21
0
 def _validate_offline(self, token, token_hashes):
     try:
         if cms.is_pkiz(token):
             verified = self._verify_pkiz_token(token, token_hashes)
         elif cms.is_asn1_token(token):
             verified = self._verify_signed_token(token, token_hashes)
         else:
             # Can't do offline validation for this type of token.
             return
     except exceptions.CertificateConfigError:
         self.log.warning(
             _LW('Fetch certificate config failed, '
                 'fallback to online validation.'))
     except exc.RevocationListError:
         self.log.warning(
             _LW('Fetch revocation list failed, '
                 'fallback to online validation.'))
     else:
         data = jsonutils.loads(verified)
         return data
Example #22
0
    def fetch_token(self, token):
        """Retrieve a token from either a PKI bundle or the identity server.

        :param str token: token id

        :raises exc.InvalidToken: if token is rejected
        """
        data = None
        token_hashes = None

        try:
            token_hashes = self._token_hashes(token)
            cached = self._cache_get_hashes(token_hashes)

            if cached:
                if cached == _CACHE_INVALID_INDICATOR:
                    self._LOG.debug('Cached token is marked unauthorized')
                    raise ksm_exceptions.InvalidToken()

                if self._check_revocations_for_cached:
                    # A token might have been revoked, regardless of initial
                    # mechanism used to validate it, and needs to be checked.
                    self._revocations.check(token_hashes)

                # NOTE(jamielennox): Cached values used to be stored as a tuple
                # of data and expiry time. They no longer are but we have to
                # allow some time to transition the old format so if it's a
                # tuple just use the data.
                if len(cached) == 2:
                    cached = cached[0]

                data = cached
            else:
                data = self._validate_offline(token, token_hashes)
                if not data:
                    data = self._identity_server.verify_token(token)

                self._token_cache.set(token_hashes[0], data)

        except (ksa_exceptions.ConnectFailure,
                ksa_exceptions.RequestTimeout,
                ksm_exceptions.RevocationListError,
                ksm_exceptions.ServiceError) as e:
            self.log.critical(_LC('Unable to validate token: %s'), e)
            raise webob.exc.HTTPServiceUnavailable()
        except ksm_exceptions.InvalidToken:
            self.log.debug('Token validation failure.', exc_info=True)
            if token_hashes:
                self._token_cache.set(token_hashes[0],
                                      _CACHE_INVALID_INDICATOR)
            self.log.warning(_LW('Authorization failed for token'))
            raise

        return data
Example #23
0
 def __init__(self, memcached_servers, log):
     self._memcached_servers = memcached_servers
     if not self._memcached_servers:
         log.warning(_LW(
             "Using the in-process token cache is deprecated as of the "
             "4.2.0 release and may be removed in the 5.0.0 release or "
             "the 'O' development cycle. The in-process cache causes "
             "inconsistent results and high memory usage. When the feature "
             "is removed the auth_token middleware will not cache tokens "
             "by default which may result in performance issues. It is "
             "recommended to use  memcache for the auth_token token cache "
             "by setting the memcached_servers option."))
 def __init__(self, memcached_servers, log):
     self._memcached_servers = memcached_servers
     if not self._memcached_servers:
         log.warning(_LW(
             "Using the in-process token cache is deprecated as of the "
             "4.2.0 release and may be removed in the 5.0.0 release or "
             "the 'O' development cycle. The in-process cache causes "
             "inconsistent results and high memory usage. When the feature "
             "is removed the auth_token middleware will not cache tokens "
             "by default which may result in performance issues. It is "
             "recommended to use  memcache for the auth_token token cache "
             "by setting the memcached_servers option."))
 def verify():
     try:
         signing_cert_path = self._signing_directory.calc_path(
             self._SIGNING_CERT_FILE_NAME)
         signing_ca_path = self._signing_directory.calc_path(
             self._SIGNING_CA_FILE_NAME)
         return cms.cms_verify(data, signing_cert_path,
                               signing_ca_path,
                               inform=inform).decode('utf-8')
     except cms.subprocess.CalledProcessError as err:
         self._LOG.warning(_LW('Verify error: %s'), err)
         raise
Example #26
0
    def verify_token(self, user_token, retry=True, allow_expired=False):
        """Authenticate user token with identity server.

        :param user_token: user's token id
        :param retry: flag that forces the middleware to retry
                      user authentication when an indeterminate
                      response is received. Optional.
        :param allow_expired: Allow retrieving an expired token.
        :returns: access info received from identity server on success
        :rtype: :py:class:`keystoneauth1.access.AccessInfo`
        :raises exc.InvalidToken: if token is rejected
        :raises exc.ServiceError: if unable to authenticate token

        """
        try:
            auth_ref = self._request_strategy.verify_token(
                user_token, allow_expired=allow_expired)
        except ksa_exceptions.NotFound as e:
            self._LOG.warning(_LW('Authorization failed for token'))
            self._LOG.warning(_LW('Identity response: %s'), e.response.text)
            raise ksm_exceptions.InvalidToken(_('Token authorization failed'))
        except ksa_exceptions.Unauthorized as e:
            self._LOG.info(_LI('Identity server rejected authorization'))
            self._LOG.warning(_LW('Identity response: %s'), e.response.text)
            if retry:
                self._LOG.info(_LI('Retrying validation'))
                return self.verify_token(user_token, False)
            msg = _('Identity server rejected authorization necessary to '
                    'fetch token data')
            raise ksm_exceptions.ServiceError(msg)
        except ksa_exceptions.HttpError as e:
            self._LOG.error(
                _LE('Bad response code while validating token: %s'),
                e.http_status)
            self._LOG.warning(_LW('Identity response: %s'), e.response.text)
            msg = _('Failed to fetch token data from identity server')
            raise ksm_exceptions.ServiceError(msg)
        else:
            return auth_ref
Example #27
0
 def verify():
     try:
         signing_cert_path = self._signing_directory.calc_path(
             self._SIGNING_CERT_FILE_NAME)
         signing_ca_path = self._signing_directory.calc_path(
             self._SIGNING_CA_FILE_NAME)
         return cms.cms_verify(data, signing_cert_path,
                               signing_ca_path,
                               inform=inform).decode('utf-8')
     except (exceptions.CMSError,
             cms.subprocess.CalledProcessError) as err:
         self._LOG.warning(_LW('Verify error: %s'), err)
         raise exc.InvalidToken(_('Token authorization failed'))
Example #28
0
    def _fetch_token(self, token):
        """Retrieve a token from either a PKI bundle or the identity server.

        :param str token: token id

        :raises exc.InvalidToken: if token is rejected
        """
        data = None
        token_hashes = None

        try:
            token_hashes = self._token_hashes(token)
            self.log.debug('XXX JD Hashed token')
            cached = self._cache_get_hashes(token_hashes)
            self.log.debug('XXX JD Cache got token')
            if cached:
                self.log.debug('XXX JD Cached token!')
                data = cached

                if self._check_revocations_for_cached:
                    # A token might have been revoked, regardless of initial
                    # mechanism used to validate it, and needs to be checked.
                    self.log.debug('XXX Checking revocation')
                    self._revocations.check(token_hashes)
            else:
                self.log.debug('XXX Validating offline')
                data = self._validate_offline(token, token_hashes)
                if not data:
                    self.log.debug('XXX Verify token on server')
                    data = self._identity_server.verify_token(token)

                self.log.debug('XXX Storing in cache token')
                self._token_cache.store(token_hashes[0], data)
                self.log.debug('XXX Stored in cache token!')
        except (ksc_exceptions.ConnectionRefused,
                ksc_exceptions.RequestTimeout,
                ksm_exceptions.RevocationListError,
                ksm_exceptions.ServiceError) as e:
            self.log.critical(_LC('Unable to validate token: %s'), e)
            raise webob.exc.HTTPServiceUnavailable()
        except ksm_exceptions.InvalidToken:
            self.log.debug('Token validation failure.', exc_info=True)
            if token_hashes:
                self._token_cache.store_invalid(token_hashes[0])
            self.log.warning(_LW('Authorization failed for token'))
            raise
        except Exception:
            self.log.critical(_LC('Unable to validate token'), exc_info=True)
            raise webob.exc.HTTPInternalServerError()

        return data
Example #29
0
    def __init__(self, app, conf):
        log = logging.getLogger(conf.get('log_name', __name__))
        log.info(_LI('Starting Keystone auth_token middleware'))

        self._conf = config.Config('auth_token', _base.AUTHTOKEN_GROUP,
                                   list_opts(), conf)

        super(AuthProtocol, self).__init__(
            app,
            log=log,
            enforce_token_bind=self._conf.get('enforce_token_bind'))

        # delay_auth_decision means we still allow unauthenticated requests
        # through and we let the downstream service make the final decision
        self._delay_auth_decision = self._conf.get('delay_auth_decision')
        self._include_service_catalog = self._conf.get(
            'include_service_catalog')
        self._hash_algorithms = self._conf.get('hash_algorithms')

        self._auth = self._create_auth_plugin()
        self._session = self._create_session()
        self._identity_server = self._create_identity_server()

        self._auth_uri = self._conf.get('auth_uri')
        if not self._auth_uri:
            self.log.warning(
                _LW('Configuring auth_uri to point to the public identity '
                    'endpoint is required; clients may not be able to '
                    'authenticate against an admin endpoint'))

            # FIXME(dolph): drop support for this fallback behavior as
            # documented in bug 1207517.

            self._auth_uri = self._identity_server.auth_uri

        self._signing_directory = _signing_dir.SigningDirectory(
            directory_name=self._conf.get('signing_dir'), log=self.log)

        self._token_cache = self._token_cache_factory()

        revocation_cache_timeout = datetime.timedelta(
            seconds=self._conf.get('revocation_cache_time'))
        self._revocations = _revocations.Revocations(revocation_cache_timeout,
                                                     self._signing_directory,
                                                     self._identity_server,
                                                     self._cms_verify,
                                                     self.log)

        self._check_revocations_for_cached = self._conf.get(
            'check_revocations_for_cached')
Example #30
0
 def verify():
     try:
         signing_cert_path = self._signing_directory.calc_path(
             self._SIGNING_CERT_FILE_NAME)
         signing_ca_path = self._signing_directory.calc_path(
             self._SIGNING_CA_FILE_NAME)
         return cms.cms_verify(data,
                               signing_cert_path,
                               signing_ca_path,
                               inform=inform).decode('utf-8')
     except (exceptions.CMSError,
             cms.subprocess.CalledProcessError) as err:
         self.log.warning(_LW('Verify error: %s'), err)
         raise exc.InvalidToken(_('Token authorization failed'))
    def __init__(self, app, conf):
        log = logging.getLogger(conf.get('log_name', __name__))
        log.info(_LI('Starting Keystone auth_token middleware'))

        # NOTE(wanghong): If options are set in paste file, all the option
        # values passed into conf are string type. So, we should convert the
        # conf value into correct type.
        self._conf = _conf_values_type_convert(conf)

        super(AuthProtocol, self).__init__(
            app,
            log=log,
            enforce_token_bind=self._conf_get('enforce_token_bind'))

        # delay_auth_decision means we still allow unauthenticated requests
        # through and we let the downstream service make the final decision
        self._delay_auth_decision = self._conf_get('delay_auth_decision')
        self._include_service_catalog = self._conf_get(
            'include_service_catalog')
        self._hash_algorithms = self._conf_get('hash_algorithms')

        self._identity_server = self._create_identity_server()

        self._auth_uri = self._conf_get('auth_uri')
        if not self._auth_uri:
            self.log.warning(
                _LW('Configuring auth_uri to point to the public identity '
                    'endpoint is required; clients may not be able to '
                    'authenticate against an admin endpoint'))

            # FIXME(dolph): drop support for this fallback behavior as
            # documented in bug 1207517.

            self._auth_uri = self._identity_server.auth_uri

        self._signing_directory = _signing_dir.SigningDirectory(
            directory_name=self._conf_get('signing_dir'), log=self.log)

        self._token_cache = self._token_cache_factory()

        revocation_cache_timeout = datetime.timedelta(
            seconds=self._conf_get('revocation_cache_time'))
        self._revocations = _revocations.Revocations(revocation_cache_timeout,
                                                     self._signing_directory,
                                                     self._identity_server,
                                                     self._cms_verify,
                                                     self.log)

        self._check_revocations_for_cached = self._conf_get(
            'check_revocations_for_cached')
Example #32
0
    def _validate_offline(self, token, token_hashes):
        if cms.is_pkiz(token):
            token_data = _uncompress_pkiz(token)
            inform = cms.PKIZ_CMS_FORM
        elif cms.is_asn1_token(token):
            token_data = cms.token_to_cms(token)
            inform = cms.PKI_ASN1_FORM
        else:
            # Can't do offline validation for this type of token.
            return

        try:
            self._revocations.check(token_hashes)
            verified = self._cms_verify(token_data, inform)
        except ksc_exceptions.CertificateConfigError:
            self.log.warning(
                _LW('Fetch certificate config failed, '
                    'fallback to online validation.'))
        except ksm_exceptions.RevocationListError:
            self.log.warning(
                _LW('Fetch revocation list failed, '
                    'fallback to online validation.'))
        else:
            data = jsonutils.loads(verified)

            audit_ids = None
            if 'access' in data:
                # It's a v2 token.
                audit_ids = data['access']['token'].get('audit_ids')
            else:
                # It's a v3 token
                audit_ids = data['token'].get('audit_ids')

            if audit_ids:
                self._revocations.check_by_audit_id(audit_ids)

            return data
Example #33
0
    def _do_fetch_token(self, token, **kwargs):
        """Helper method to fetch a token and convert it into an AccessInfo."""
        if self.kwargs_to_fetch_token:
            data = self.fetch_token(token, **kwargs)
        else:
            m = _('Implementations of auth_token must set '
                  'kwargs_to_fetch_token this will be the required and '
                  'assumed in Pike.')
            warnings.warn(m)
            data = self.fetch_token(token)

        try:
            return data, access.create(body=data, auth_token=token)
        except Exception:
            self.log.warning(_LW('Invalid token contents.'), exc_info=True)
            raise ksm_exceptions.InvalidToken(_('Token authorization failed'))
Example #34
0
    def get_target_resource(self, req):
        """Retrieve target information.

        If discovery is enabled, target will attempt to retrieve information
        from service catalog. If not, the information will be taken from
        given config file.
        """
        service_info = Service(type=taxonomy.UNKNOWN,
                               name=taxonomy.UNKNOWN,
                               id=taxonomy.UNKNOWN,
                               admin_endp=None,
                               private_endp=None,
                               public_endp=None)

        catalog = {}
        try:
            catalog = jsonutils.loads(req.environ['HTTP_X_SERVICE_CATALOG'])
        except KeyError:
            msg = _LW('Unable to discover target information because '
                      'service catalog is missing. Either the incoming '
                      'request does not contain an auth token or auth '
                      'token does not contain a service catalog. For '
                      'the latter, please make sure the '
                      '"include_service_catalog" property in '
                      'auth_token middleware is set to "True"')
            self._log.warning(msg)

        default_endpoint = None
        for endp in catalog:
            endpoint_urls = endp['endpoints'][0]
            admin_urlparse = urlparse.urlparse(
                endpoint_urls.get('adminURL', ''))
            public_urlparse = urlparse.urlparse(
                endpoint_urls.get('publicURL', ''))
            req_url = urlparse.urlparse(req.host_url)
            if (req_url.netloc == admin_urlparse.netloc
                    or req_url.netloc == public_urlparse.netloc):
                service_info = self._get_service_info(endp)
                break
            elif (self._MAP.default_target_endpoint_type
                  and endp['type'] == self._MAP.default_target_endpoint_type):
                default_endpoint = endp
        else:
            if default_endpoint:
                service_info = self._get_service_info(default_endpoint)
        return self._build_target(req, service_info)
Example #35
0
    def fetch_token(self, token):
        """Retrieve a token from either a PKI bundle or the identity server.

        :param str token: token id

        :raises exc.InvalidToken: if token is rejected
        """
        try:
            token_hashes = self._token_hashes(token)
            offline_data = self._validate_offline(token, token_hashes)

            if offline_data:
                # NOTE(jamielennox): If we've validated a PKI token we don't
                # need to cache it, and revocation check was already performed.
                return offline_data

            cached = self._token_cache.get_first(*token_hashes)

            if cached:
                if self._check_revocations_for_cached:
                    # A token might have been revoked, regardless of initial
                    # mechanism used to validate it, and needs to be checked.
                    self._revocations.check(token_hashes)

                return cached

            data = self._identity_server.verify_token(token)
            self._token_cache.store(token_hashes[0], data)
            return data

        except (ksa_exceptions.ConnectFailure,
                ksa_exceptions.RequestTimeout,
                ksm_exceptions.RevocationListError,
                ksm_exceptions.ServiceError) as e:
            self.log.critical(_LC('Unable to validate token: %s'), e)
            raise webob.exc.HTTPServiceUnavailable()
        except ksm_exceptions.InvalidToken:
            self.log.debug('Token validation failure.', exc_info=True)
            if token_hashes:
                self._token_cache.store_invalid(token_hashes[0])
            self.log.warning(_LW('Authorization failed for token'))
            raise
        except Exception:
            self.log.critical(_LC('Unable to validate token'), exc_info=True)
            raise webob.exc.HTTPInternalServerError()
    def __init__(self, app, conf):
        self._LOG = logging.getLogger(conf.get('log_name', __name__))
        self._LOG.info(_LI('Starting Keystone auth_token middleware'))
        # NOTE(wanghong): If options are set in paste file, all the option
        # values passed into conf are string type. So, we should convert the
        # conf value into correct type.
        self._conf = _conf_values_type_convert(conf)
        self._app = app

        # delay_auth_decision means we still allow unauthenticated requests
        # through and we let the downstream service make the final decision
        self._delay_auth_decision = self._conf_get('delay_auth_decision')
        self._include_service_catalog = self._conf_get(
            'include_service_catalog')

        self._identity_server = self._create_identity_server()

        self._auth_uri = self._conf_get('auth_uri')
        if not self._auth_uri:
            self._LOG.warning(
                _LW('Configuring auth_uri to point to the public identity '
                    'endpoint is required; clients may not be able to '
                    'authenticate against an admin endpoint'))

            # FIXME(dolph): drop support for this fallback behavior as
            # documented in bug 1207517.

            self._auth_uri = self._identity_server.auth_uri

        self._signing_directory = _signing_dir.SigningDirectory(
            directory_name=self._conf_get('signing_dir'), log=self._LOG)

        self._token_cache = self._token_cache_factory()

        revocation_cache_timeout = datetime.timedelta(
            seconds=self._conf_get('revocation_cache_time'))
        self._revocations = _revocations.Revocations(revocation_cache_timeout,
                                                     self._signing_directory,
                                                     self._identity_server,
                                                     self._cms_verify,
                                                     self._LOG)

        self._check_revocations_for_cached = self._conf_get(
            'check_revocations_for_cached')
        self._init_auth_headers()
Example #37
0
    def _get_user_token_from_header(self, env):
        """Get token id from request.

        :param env: wsgi request environment
        :returns: token id
        :raises exc.InvalidToken: if no token is provided in request

        """
        token = self._get_header(env, 'X-Auth-Token',
                                 self._get_header(env, 'X-Storage-Token'))
        if token:
            return token
        else:
            if not self._delay_auth_decision:
                self._LOG.warn(_LW('Unable to find authentication token'
                                   ' in headers'))
                self._LOG.debug('Headers: %s', env)
            raise exc.InvalidToken(_('Unable to find token in headers'))
    def _get_user_token_from_header(self, env):
        """Get token id from request.

        :param env: wsgi request environment
        :returns: token id
        :raises exc.InvalidToken: if no token is provided in request

        """
        token = self._get_header(env, 'X-Auth-Token',
                                 self._get_header(env, 'X-Storage-Token'))
        if token:
            return token
        else:
            if not self._delay_auth_decision:
                self._LOG.warn(_LW('Unable to find authentication token'
                                   ' in headers'))
                self._LOG.debug('Headers: %s', env)
            raise exc.InvalidToken(_('Unable to find token in headers'))
    def __init__(self, app, conf):
        log = logging.getLogger(conf.get('log_name', __name__))
        log.info(_LI('Starting Keystone auth_token middleware'))

        # NOTE(wanghong): If options are set in paste file, all the option
        # values passed into conf are string type. So, we should convert the
        # conf value into correct type.
        self._conf = _conf_values_type_convert(conf)

        # NOTE(sileht, cdent): If we don't want to use oslo.config global
        # object there are two options: set "oslo_config_project" in
        # paste.ini and the middleware will load the configuration with a
        # local oslo.config object or the caller which instantiates
        # AuthProtocol can pass in an existing oslo.config as the
        # value of the "oslo_config_config" key in conf. If both are
        # set "olso_config_config" is used.
        self._local_oslo_config = conf.get('oslo_config_config')
        if (not self._local_oslo_config) and ('oslo_config_project' in conf):
            if 'oslo_config_file' in conf:
                default_config_files = [conf['oslo_config_file']]
            else:
                default_config_files = None
            self._local_oslo_config = cfg.ConfigOpts()
            self._local_oslo_config(
                [], project=conf['oslo_config_project'],
                default_config_files=default_config_files,
                validate_default_values=True)

        if self._local_oslo_config:
            self._local_oslo_config.register_opts(_OPTS,
                                                  group=_base.AUTHTOKEN_GROUP)
            self._local_oslo_config.register_opts(_auth.OPTS,
                                                  group=_base.AUTHTOKEN_GROUP)

            loading.register_auth_conf_options(self._local_oslo_config,
                                               group=_base.AUTHTOKEN_GROUP)

        super(AuthProtocol, self).__init__(
            app,
            log=log,
            enforce_token_bind=self._conf_get('enforce_token_bind'))

        # delay_auth_decision means we still allow unauthenticated requests
        # through and we let the downstream service make the final decision
        self._delay_auth_decision = self._conf_get('delay_auth_decision')
        self._include_service_catalog = self._conf_get(
            'include_service_catalog')
        self._hash_algorithms = self._conf_get('hash_algorithms')

        self._identity_server = self._create_identity_server()

        self._auth_uri = self._conf_get('auth_uri')
        if not self._auth_uri:
            self.log.warning(
                _LW('Configuring auth_uri to point to the public identity '
                    'endpoint is required; clients may not be able to '
                    'authenticate against an admin endpoint'))

            # FIXME(dolph): drop support for this fallback behavior as
            # documented in bug 1207517.

            self._auth_uri = self._identity_server.auth_uri

        self._signing_directory = _signing_dir.SigningDirectory(
            directory_name=self._conf_get('signing_dir'), log=self.log)

        self._token_cache = self._token_cache_factory()

        revocation_cache_timeout = datetime.timedelta(
            seconds=self._conf_get('revocation_cache_time'))
        self._revocations = _revocations.Revocations(revocation_cache_timeout,
                                                     self._signing_directory,
                                                     self._identity_server,
                                                     self._cms_verify,
                                                     self.log)

        self._check_revocations_for_cached = self._conf_get(
            'check_revocations_for_cached')
Example #40
0
    def _validate_token(self, token, env, retry=True):
        """Authenticate user token

        :param token: token id
        :param env: wsgi environment
        :param retry: Ignored, as it is not longer relevant
        :returns: uncrypted body of the token if the token is valid
        :raises exc.InvalidToken: if token is rejected

        """
        token_id = None

        try:
            token_ids, cached = self._token_cache.get(token)
            token_id = token_ids[0]
            if cached:
                # Token was retrieved from the cache. In this case, there's no
                # need to check that the token is expired because the cache
                # fetch fails for an expired token. Also, there's no need to
                # put the token in the cache because it's already in the cache.

                data = cached

                if self._check_revocations_for_cached:
                    # A token stored in Memcached might have been revoked
                    # regardless of initial mechanism used to validate it,
                    # and needs to be checked.
                    self._revocations.check(token_ids)
                self._confirm_token_bind(data, env)
            else:
                verified = None
                # Token wasn't cached. In this case, the token needs to be
                # checked that it's not expired, and also put in the cache.
                try:
                    if cms.is_pkiz(token):
                        verified = self._verify_pkiz_token(token, token_ids)
                    elif cms.is_asn1_token(token):
                        verified = self._verify_signed_token(token, token_ids)
                except exceptions.CertificateConfigError:
                    self._LOG.warn(_LW('Fetch certificate config failed, '
                                       'fallback to online validation.'))
                except exc.RevocationListError:
                    self._LOG.warn(_LW('Fetch revocation list failed, '
                                       'fallback to online validation.'))

                if verified is not None:
                    data = jsonutils.loads(verified)

                    audit_ids = None
                    if 'access' in data:
                        # It's a v2 token.
                        audit_ids = data['access']['token'].get('audit_ids')
                    else:
                        # It's a v3 token
                        audit_ids = data['token'].get('audit_ids')

                    if audit_ids:
                        self._revocations.check_by_audit_id(audit_ids)

                    expires = _get_token_expiration(data)
                    _confirm_token_not_expired(expires)
                else:
                    data = self._identity_server.verify_token(token, retry)
                    # No need to confirm token expiration here since
                    # verify_token fails for expired tokens.
                    expires = _get_token_expiration(data)
                self._confirm_token_bind(data, env)
                self._token_cache.store(token_id, data, expires)
            return data
        except (exceptions.ConnectionRefused, exceptions.RequestTimeout,
                exc.RevocationListError, exc.ServiceError) as e:
            self._LOG.critical(_LC('Unable to validate token: %s'), e)
            raise ServiceError()
        except exc.InvalidToken:
            self._LOG.debug('Token validation failure.', exc_info=True)
            if token_id:
                self._token_cache.store_invalid(token_id)
            self._LOG.warning(_LW('Authorization failed for token'))
            raise
        except Exception:
            self._LOG.critical(_LC('Unable to validate token'), exc_info=True)
            raise exc._InternalServiceError()
Example #41
0
    def _validate_token(self, token, env):
        """Authenticate user token

        :param token: token id
        :param env: wsgi environment
        :returns: uncrypted body of the token if the token is valid
        :raises exc.InvalidToken: if token is rejected

        """
        token_hashes = None

        try:
            token_hashes = self._token_hashes(token)
            cached = self._cache_get_hashes(token_hashes)

            if cached:
                data = cached

                if self._check_revocations_for_cached:
                    # A token stored in Memcached might have been revoked
                    # regardless of initial mechanism used to validate it,
                    # and needs to be checked.
                    self._revocations.check(token_hashes)

            else:
                verified = None

                try:
                    if cms.is_pkiz(token):
                        verified = self._verify_pkiz_token(token, token_hashes)
                    elif cms.is_asn1_token(token):
                        verified = self._verify_signed_token(token,
                                                             token_hashes)
                except exceptions.CertificateConfigError:
                    self._LOG.warning(_LW('Fetch certificate config failed, '
                                          'fallback to online validation.'))
                except exc.RevocationListError:
                    self._LOG.warning(_LW('Fetch revocation list failed, '
                                          'fallback to online validation.'))

                if verified is not None:
                    data = jsonutils.loads(verified)
                else:
                    data = self._identity_server.verify_token(token)

            auth_ref = access.AccessInfo.factory(body=data, auth_token=token)

            # 0 seconds of validity means is it valid right now.
            if auth_ref.will_expire_soon(stale_duration=0):
                raise exc.InvalidToken(_('Token authorization failed'))

            if _token_is_v2(data) and not auth_ref.project_id:
                msg = _('Unable to determine service tenancy.')
                raise exc.InvalidToken(msg)

            self._confirm_token_bind(data, env)

            if not cached:
                self._token_cache.store(token_hashes[0], data)

            return auth_ref, data
        except (exceptions.ConnectionRefused, exceptions.RequestTimeout):
            self._LOG.debug('Token validation failure.', exc_info=True)
            self._LOG.warning(_LW('Authorization failed for token'))
            raise exc.InvalidToken(_('Token authorization failed'))
        except exc.ServiceError:
            raise
        except Exception:
            self._LOG.debug('Token validation failure.', exc_info=True)
            if token_hashes:
                self._token_cache.store_invalid(token_hashes[0])
            self._LOG.warning(_LW('Authorization failed for token'))
            raise exc.InvalidToken(_('Token authorization failed'))
Example #42
0
    def __init__(self, app, conf):
        log = logging.getLogger(conf.get('log_name', __name__))
        log.info(_LI('Starting Keystone auth_token middleware'))

        # NOTE(wanghong): If options are set in paste file, all the option
        # values passed into conf are string type. So, we should convert the
        # conf value into correct type.
        self._conf = _conf_values_type_convert(conf)

        # NOTE(sileht): If we don't want to use oslo.config global object
        # we can set the paste "oslo_config_project" and the middleware
        # will load the configuration with a local oslo.config object.
        self._local_oslo_config = None
        if 'oslo_config_project' in conf:
            if 'oslo_config_file' in conf:
                default_config_files = [conf['oslo_config_file']]
            else:
                default_config_files = None

            # For unit tests, support passing in a ConfigOpts in
            # oslo_config_config.
            self._local_oslo_config = conf.get('oslo_config_config',
                                               cfg.ConfigOpts())
            self._local_oslo_config({},
                                    project=conf['oslo_config_project'],
                                    default_config_files=default_config_files,
                                    validate_default_values=True)

            self._local_oslo_config.register_opts(_OPTS,
                                                  group=_base.AUTHTOKEN_GROUP)
            auth.register_conf_options(self._local_oslo_config,
                                       group=_base.AUTHTOKEN_GROUP)

        super(AuthProtocol, self).__init__(
            app,
            log=log,
            enforce_token_bind=self._conf_get('enforce_token_bind'))

        # delay_auth_decision means we still allow unauthenticated requests
        # through and we let the downstream service make the final decision
        self._delay_auth_decision = self._conf_get('delay_auth_decision')
        self._include_service_catalog = self._conf_get(
            'include_service_catalog')
        self._hash_algorithms = self._conf_get('hash_algorithms')

        self._identity_server = self._create_identity_server()

        self._auth_uri = self._conf_get('auth_uri')
        if not self._auth_uri:
            self.log.warning(
                _LW('Configuring auth_uri to point to the public identity '
                    'endpoint is required; clients may not be able to '
                    'authenticate against an admin endpoint'))

            # FIXME(dolph): drop support for this fallback behavior as
            # documented in bug 1207517.

            self._auth_uri = self._identity_server.auth_uri

        self._signing_directory = _signing_dir.SigningDirectory(
            directory_name=self._conf_get('signing_dir'), log=self.log)

        self._token_cache = self._token_cache_factory()

        revocation_cache_timeout = datetime.timedelta(
            seconds=self._conf_get('revocation_cache_time'))
        self._revocations = _revocations.Revocations(revocation_cache_timeout,
                                                     self._signing_directory,
                                                     self._identity_server,
                                                     self._cms_verify,
                                                     self.log)

        self._check_revocations_for_cached = self._conf_get(
            'check_revocations_for_cached')
Example #43
0
    def __init__(self, app, conf):
        # tomograph.start("AuthProtocol", "init", "127.0.0.1", 0)

        log = logging.getLogger(conf.get("log_name", __name__))
        log.info(_LI("Starting Keystone auth_token middleware"))

        # NOTE(wanghong): If options are set in paste file, all the option
        # values passed into conf are string type. So, we should convert the
        # conf value into correct type.
        self._conf = _conf_values_type_convert(conf)

        # NOTE(sileht): If we don't want to use oslo.config global object
        # we can set the paste "oslo_config_project" and the middleware
        # will load the configuration with a local oslo.config object.
        self._local_oslo_config = None
        if "oslo_config_project" in conf:
            if "oslo_config_file" in conf:
                default_config_files = [conf["oslo_config_file"]]
            else:
                default_config_files = None

            self._local_oslo_config = cfg.ConfigOpts()
            self._local_oslo_config(
                {},
                project=conf["oslo_config_project"],
                default_config_files=default_config_files,
                validate_default_values=True,
            )

            self._local_oslo_config.register_opts(_OPTS, group=_base.AUTHTOKEN_GROUP)
            auth.register_conf_options(self._local_oslo_config, group=_base.AUTHTOKEN_GROUP)

        # tomograph.annotate("call super", "AuthProtocol")
        super(AuthProtocol, self).__init__(app, log=log, enforce_token_bind=self._conf_get("enforce_token_bind"))

        # delay_auth_decision means we still allow unauthenticated requests
        # through and we let the downstream service make the final decision
        self._delay_auth_decision = self._conf_get("delay_auth_decision")
        self._include_service_catalog = self._conf_get("include_service_catalog")
        self._hash_algorithms = self._conf_get("hash_algorithms")

        # tomograph.annotate("create identity server", "AuthProtocol")
        self._identity_server = self._create_identity_server()

        self._auth_uri = self._conf_get("auth_uri")
        if not self._auth_uri:
            self.log.warning(
                _LW(
                    "Configuring auth_uri to point to the public identity "
                    "endpoint is required; clients may not be able to "
                    "authenticate against an admin endpoint"
                )
            )

            # FIXME(dolph): drop support for this fallback behavior as
            # documented in bug 1207517.

            self._auth_uri = self._identity_server.auth_uri

        self._signing_directory = _signing_dir.SigningDirectory(
            directory_name=self._conf_get("signing_dir"), log=self.log
        )

        self._token_cache = self._token_cache_factory()

        revocation_cache_timeout = datetime.timedelta(seconds=self._conf_get("revocation_cache_time"))
        self._revocations = _revocations.Revocations(
            revocation_cache_timeout, self._signing_directory, self._identity_server, self._cms_verify, self.log
        )

        self._check_revocations_for_cached = self._conf_get("check_revocations_for_cached")
Example #44
0
    def process_request(self, request):
        """Process request.

        If this method returns a value then that value will be used as the
        response. The next application down the stack will not be executed and
        process_response will not be called.

        Otherwise, the next application down the stack will be executed and
        process_response will be called with the generated response.

        By default this method does not return a value.

        :param request: Incoming request
        :type request: _request.AuthTokenRequest

        """
        user_auth_ref = None
        serv_auth_ref = None
        allow_expired = False

        if request.service_token:
            self.log.debug('Authenticating service token')
            try:
                _, serv_auth_ref = self._do_fetch_token(request.service_token)
                self._validate_token(serv_auth_ref)
                self._confirm_token_bind(serv_auth_ref, request)
            except ksm_exceptions.InvalidToken:
                self.log.info(_LI('Invalid service token'))
                request.service_token_valid = False
            else:
                # FIXME(jamielennox): The new behaviour for service tokens is
                # that they have to pass the policy check to be allowed.
                # Previously any token was accepted here. For now we will
                # continue to mark service tokens as valid if they are valid
                # but we will only allow service role tokens to do
                # allow_expired. In future we should reject any token that
                # isn't a service token here.
                role_names = set(serv_auth_ref.role_names)
                check = self._service_token_roles.intersection(role_names)
                role_check_passed = bool(check)

                # if service_token_role_required then the service token is only
                # valid if the roles check out. Otherwise at this point it is
                # true because keystone has already validated it.
                if self._service_token_roles_required:
                    request.service_token_valid = role_check_passed
                else:
                    if not self._service_token_warning_emitted:
                        self.log.warning(
                            _LW('A valid token was submitted as '
                                'a service token, but it was not '
                                'a valid service token. This is '
                                'incorrect but backwards '
                                'compatible behaviour. This will '
                                'be removed in future releases.'))
                        # prevent log spam on every single request
                        self._service_token_warning_emitted = True

                    request.service_token_valid = True

                # allow_expired always requires passing the role check.
                allow_expired = role_check_passed

        if request.user_token:
            self.log.debug('Authenticating user token')
            try:
                data, user_auth_ref = self._do_fetch_token(
                    request.user_token, allow_expired=allow_expired)
                self._validate_token(user_auth_ref,
                                     allow_expired=allow_expired)
                if not request.service_token:
                    self._confirm_token_bind(user_auth_ref, request)
            except ksm_exceptions.InvalidToken:
                self.log.info(_LI('Invalid user token'))
                request.user_token_valid = False
            else:
                request.user_token_valid = True
                request.token_info = data

        request.token_auth = _user_plugin.UserAuthPlugin(
            user_auth_ref, serv_auth_ref)
    def _validate_token(self, token, env):
        """Authenticate user token

        :param token: token id
        :param env: wsgi environment
        :returns: uncrypted body of the token if the token is valid
        :raises exc.InvalidToken: if token is rejected

        """
        token_id = None

        try:
            token_ids, cached = self._token_cache.get(token)
            token_id = token_ids[0]
            if cached:
                # Token was retrieved from the cache. In this case, there's no
                # need to check that the token is expired because the cache
                # fetch fails for an expired token. Also, there's no need to
                # put the token in the cache because it's already in the cache.

                data = cached

                if self._check_revocations_for_cached:
                    # A token stored in Memcached might have been revoked
                    # regardless of initial mechanism used to validate it,
                    # and needs to be checked.
                    self._revocations.check(token_ids)
                self._confirm_token_bind(data, env)
            else:
                verified = None
                # Token wasn't cached. In this case, the token needs to be
                # checked that it's not expired, and also put in the cache.
                try:
                    if cms.is_pkiz(token):
                        verified = self._verify_pkiz_token(token, token_ids)
                    elif cms.is_asn1_token(token):
                        verified = self._verify_signed_token(token, token_ids)
                except exceptions.CertificateConfigError:
                    self._LOG.warn(_LW('Fetch certificate config failed, '
                                       'fallback to online validation.'))
                except exc.RevocationListError:
                    self._LOG.warn(_LW('Fetch revocation list failed, '
                                       'fallback to online validation.'))

                if verified is not None:
                    data = jsonutils.loads(verified)
                    expires = _get_token_expiration(data)
                    _confirm_token_not_expired(expires)
                else:
                    data = self._identity_server.verify_token(token)
                    # No need to confirm token expiration here since
                    # verify_token fails for expired tokens.
                    expires = _get_token_expiration(data)
                self._confirm_token_bind(data, env)
                self._token_cache.store(token_id, data, expires)
            return data
        except (exceptions.ConnectionRefused, exceptions.RequestTimeout):
            self._LOG.debug('Token validation failure.', exc_info=True)
            self._LOG.warn(_LW('Authorization failed for token'))
            raise exc.InvalidToken(_('Token authorization failed'))
        except exc.ServiceError:
            raise
        except Exception:
            self._LOG.debug('Token validation failure.', exc_info=True)
            if token_id:
                self._token_cache.store_invalid(token_id)
            self._LOG.warn(_LW('Authorization failed for token'))
            raise exc.InvalidToken(_('Token authorization failed'))