Customizing the OpenStack Keystone Authentication Backend

OpenStack Login For those of you unfamiliar with OpenStack, it is a collection of many independent pieces of cloud software, and they all tie into Keystone for user authentication and authorization. Keystone uses a MySQL database backend by default, and has some support for LDAP out-of-the-box. But what if you want to have it authenticate against some other service? Fortunately, the Keystone developers have already created a way to do that fairly easily; however, they haven’t documented it yet. Here’s how I did it:

  1. Grab the Keystone source from GitHub and checkout a stable branch:
    % git clone git://github.com/openstack/keystone.git
    % cd keystone
    % git checkout stable/grizzly
    
  2. Since we still want to use the MySQL backend for user authorization, we will extend the default identity driver, keystone.identity.backends.sql.Identity, and simply override the password checking function. Create a new file called keystone/identity/backends/custom.py containing:
    from __future__ import absolute_import
    import pam
    from . import sql

    class Identity(sql.Identity):
        def _check_password(self, password, user_ref):
            username = user_ref.get('name')
           
            if (username in ['admin', 'nova', 'swift']):
                return super(Identity, self)._check_password(password, user_ref)
           
            return pam.authenticate(username, password)

    In this snippet, we check the username and password against PAM, but that can be anything you want (Kerberos, Active Directory, LDAP, a flat file, etc.). If the username is one of the OpenStack service accounts, then the code uses the normal Keystone logic and checks it against the MySQL database.

  3. Build and install the code:
    % python setup.py build
    % sudo python setup.py install
    
  4. Configure Keystone to use the custom identity driver. In /etc/keystone/keystone.conf add or change the following section:
    [identity]
    driver = keystone.identity.backends.custom.Identity
  5. Start Keystone (keystone-all) and test, then save the changes to the Keystone source:
    % git add keystone/identity/backends/custom.py
    % git commit -m "Created custom identity driver" -a
    

And that’s it. In reality, I would probably fork the Keystone repository on GitHub and create a new branch for this work (git checkout -b customauth stable/grizzly), but that’s not really necessary. Actually, you could probably even get away with not recompiling Keystone. Just put the custom class somewhere in Keystone’s PYTHONPATH. But I’m not a Python expert, so maybe that wouldn’t work. Either way, I like having everything together, and Git makes it brainless to maintain customizations to large projects.

9 thoughts on “Customizing the OpenStack Keystone Authentication Backend

    • Their tenant would be whatever you set it to be in the Keystone database. All of the users would still need to be created in the Keystone database; it’s just the authentication that is performed by PAM, in this example.

      It would be a whole other exercise to configure Keystone to use some other data source for users, tenants, roles, etc., and one that I don’t think is worthwhile considering the volatile nature of the Keystone schema. If you need to add all of the users in your organization to OpenStack, it wouldn’t be too hard to write a cron job to sync users to Keystone from an external data source, or better yet, use an identity management system to manage Keystone users.

      Thanks for the comment.

      • Thanks for your reply. Indeed I have to add users for an active directory in OpenStack. However I can’t modify the active directory configuration to do this. So your solution (cron job for import users in a SQL database + pam authentification) seems to be good 🙂

  1. Another alternative to a cron/sync job would be to add the PAM authenticated user to the keystone database at the time of first authentication with probably a default tenant id to start with.

  2. Hi,

    Can we intergrate keystone service with an external application which has its own authentication mechanism. This application is using MS SQL server DB.

    Thanks

    • I would imagine that would be possible. The method this post outlines should allow you fairly easily implement almost any authentication scheme. If you can code it, you can do it.

  3. I’ve tried this with the current keystone (version 0.4.1.11) via devstack and when I try to list users, I get an error saying custom.py can’t find the pam module:

    (keystone-all): 2013-11-21 11:57:43,002 DEBUG cfg log_opt_values identity.driver = keystone.identity.backends.custom.Identity
    (keystone-all): 2013-11-21 11:57:43,003 DEBUG cfg log_opt_values identity.max_password_length = 4096
    (keystone-all): 2013-11-21 11:57:43,003 DEBUG cfg log_opt_values ********************************************************************************
    (keystone.common.environment): 2013-11-21 11:57:43,004 INFO __init__ wrapper Environment configured as: eventlet
    (keystone): 2013-11-21 11:57:43,118 CRITICAL log logging_excepthook No module named pam

    Linking keystone/identity/backends/pam.py to /usr/local/lib/python2.7/dist-packages/pam.py gets me past this error, but when I try to authenticate against keystone, it says module has no attribute ‘authenticate’.

    Any suggestions you might have would be appreciated.

    • import pam refers to the Python pam module which, for me, was pulled in as a dependency of Keystone automatically. Unfortunately, there is also a Keystone identity backend called ‘pam’, which is why you must tell Python to give priority to the system module path (from __future__ import absolute_import). Otherwise, it just tries to load the identity module from the same directory.

      There may be a better way of telling Python to use the system’s pam module—I am not a Python expert—this is just how I got it to work.

Leave a Reply