Jump to: navigation, search


Pluggable Identity Authentication Handlers

Related Links


The functionality described in this blueprint aims to drive additional value into Keystone/OpenStack by adding support for pluggable authentication in a manner that's transparent across concrete identity drivers irrespective of the driver/backing-store type (LDAP, SQL, etc.). It also sets forth the constructs necessary to permit authentication by means other than user id/password, and moreover to perform authentication in a conditional manner based a given request. Together these enhancements pave the way for numerous authentication mechanisms moving forward.

The following list recaps the current high level scheme used by Keystone in terms of authentication:

  • The user id and password to authenticate are passed in on the request body to a call on the POST /tokens service.
  • The underlying concrete identity driver's (LDAP, SQL, etc.) authenticate method is called with the user_id, tenant_id and password (from the request) to determine if the credentials can be authenticated.
  • The concrete identity driver validates the passed credentials using it's underlying 'store'. For example, the SQL driver verifies the password given matches that in the underlying SQL based store; the LDAP driver verifies the credentials can be use to bind the user dn, etc..
  • If the authentication is successful, the drive return a tuple which includes a reference to the user, tenant and meta information from the underlying store.
  • The meta tuple returned from the driver's authenticate method is used to build a token which is returned as a response to the POST /tokens request.

Although the current scheme for authentication in Keystone based identity drivers works for a number of use cases, it leaves room for improvement in a few key areas we look to address as part of this enhancement:

  • Authentication is based on userId/password credentials -- there is no direct means to support other schemes.
  • The authentication is tightly coupled with the identity driver itself -- the authenticate method lives right on the driver interface and hence its not clean to plug-in new authentication methods irrespective of the concrete driver.
  • There is no direct means to support the notion of conditional authentication -- the ability to try a number of authentication schemes which are conditionally applicable to a given request.

Note -- I have termed this functionality 'Pluggable Authentication Handler' rather than 'Pluggable Authentication Module' (PAM) to remove any confusion between what we might expect in terms of semantics from traditional Unix style PAM. That is, although this design may highly resemble traditional PAM behavior, it will likely not be semantically equivalent and hence I have not termed it 'PAM'.

Core Use Cases

This section lists some of the core use cases we are looking to support as part of this blueprint.

  • A) As a keystone admin, I want the ability to specify 1 or more authentication handlers in keystone.conf that can conditionally be used to authenticate a POST /tokens request in the keystone service.
  • B) As a keystone admin, I want to be able to use keystone authentication handlers irrespective of the concrete identity driver type (SQL, LDAP, KVS, etc.).
  • C) As an keystone authentication developer, I want my module implementation to have access to the request body/header in the POST /tokens request so that I can get/put properties from/into it and also conditionally determine if my module is applicable for the current request.
  • D) As a keystone authentication developer, I want my module implementation to have a reference to the concrete identity driver being used so that I can interface with it as needed.
  • E) As a keystone admin, I want my concrete identity driver's authentication functionality used (by default) when no authentication handlers are specified in keystone.conf (i.e. what we have today) or when none of the handlers are applicable to the given POST /tokens request.

Technical/Implementation Notes

This section outlines some technical and/or implementation notes. The notes here are not meant to describe how the implementation must be done, but rather to provide further information on how it might be done and convey technical details with code snippets and such.

Implementing Authentication Handlers

We could provide a base class or interface for authentication handlers to implement in order to be considered an authN handler. As indicated in the use cases, this handler needs a way to get at (minimally) the request header which might be used to determine if the handler is applicable and also to get any transported authN properties. Example:

class AuthenticationDriver():
    """Base class for pluggable authN handlers.
        Concrete handlers should implement the
        authenticate method which is called 
        during the identity authentication

    def __init__(self, identity_api):
        self.identity_api = identity_api
    def authenticate(self, context=None, user_id=None, tenant_id=None, 
        """Perform authentication. The
            context should include the request
            :returns: True if the request
            is applicable to this handler and
            the request is authenticated successfully.
            False if the request is not applicable to
            this handler.
            :raises: keystone.exception.NotAuthorized
            If this handler is applicable but the 
            request is unauthorized.
        raise exception.NotImplemented()

Then if we assume the authN handlers have been parsed from the config file and created, the authentication logic might look something like this:

        for authn_handler in handlers:
            if authn_handler .authenticate(context, user_id, tenant_id, password):
                return self.driver._get_user_meta(user_id, tenant_id)
        # no applicable hanlder, identity driver authn
        return self.identity_api.authenticate(context=context,

Specifying Authentication Handlers To Use

I was thinking that keystone.conf could have a new section/group which is used for authentication properties. For a simple/initial impl, this might just have a single (comma separate) list property to define the class names of the authN handlers to use in order of their conditional usage. Example:

handlers = keystone.identity.authN.simple_token.SimpleTokenAuthentication,

Refactoring In Identity Driver

It might be worthwhile to consider adding a new method to the Identity Driver interface which returns the user meta without doing the actual authentication. That is, it returns the tuple that the current drivers authenticate method does, but it does not do the authN. This could then be called by consumers to get the meta for a user that has been authN'd by a handler so the handler or other consumer does not need to know how to build it.


This section records any questions/answers which have been posed to date.

Q (Dolph)::
For use case 3, why do you need access to the full body? There's only certain attributes in the request body that would be applicable. What are you interested in, specifically?
A (Boden)::
My specific case only requires access to the request header as the header is transporting some properties in the authentication plug-in. However the reason I stated the body as well is that I was envisioning someone in the future needing access to the body. However at this point I don't have a use case that would justify exposing the request body so I have no arguments to just assuming the authN handlers can access the request headers.

Q (Dolph)::
For use case 5, how about extracting authentication from the drivers (they're largely similar), and instead making even username+password and token credentials configurable authentication callables in keystone.conf (and providing them out of the box as the two default, of course)?
A (Boden)::
Use case 5 was just about currency with our current behavior. That is, even with pluggable authN, I as a Keystone admin should get the same behavior we have today out of the box if I don't want to plug in any 'custom' handlers and I shouldn't have to do anything extra to get this default behavior.