dimanche 12 juin 2016

Erratic INSUFFICIENT_ACCESS error when using LDAP


We have a Pyramid application that uses LDAP for authentication. Currently we don't use pyramid_ldap but custom code based on python_ldap to connect to the LDAP server.

The code is in a Singleton class: the __init__ method (more precisely the method called during instance creation) calls ldap.initialize to create the LDAPObject and simple_bind_s. Here is the code (the user can by pass the LDAP server by providing a local admin account):

class Singleton(object):
    '''
    Implements the singleton pattern. A class deriving from ``Singleton`` can
    have only one instance. The first instanciation will create an object and
    other instanciations return the same object. Note that the :py:meth:`__init__`
    method (if any) is still called at each instanciation (on the same object).
    Therefore, :py:class:`Singleton` derived classes should define
    :py:meth:`__singleton_init__`
    instead of :py:meth:`__init__` because the former is only called once.
    '''
    @classmethod
    def get_instance(cls):
        try:
            return getattr(cls, '_singleton_instance')
        except AttributeError:
            msg = "Class %s has not been initialized" % cls.__name__
            raise ValueError(msg)

    def __new__(cls, *args, **kwargs):
        if '_singleton_instance' not in cls.__dict__:
            cls._singleton_instance = super(Singleton, cls).__new__(cls)
            singleton_init = getattr(cls._singleton_instance,
                                 '__singleton_init__', None)
            if singleton_init is not None:
                singleton_init(*args, **kwargs)
        return cls._singleton_instance

    def __init__(self, *args, **kwargs):
        '''
        The __init__ method of :py:class:`Singleton` derived class should do nothing.
        Derived classes must define :py:meth:`__singleton_init__` instead of __init__.
        '''

    def __singleton_init__(self, *args, **kwargs):
        super(Singleton, self).__init__(*args, **kwargs)

class UsersAndGroups(Singleton):
    """
    Class used to query the LDAP directory.
    """
    def __singleton_init__(self, admin_login, admin_password,
                           server, ldap_admin_dn, ldap_password, users_dn,
                           groups_dn):
        self.admin_login = admin_login
        self.admin_password = admin_password
        self.server = server
        self.ldap_admin_dn = ldap_admin_dn
        self.ldap_password = ldap_password
        self.users_dn = users_dn
        self.groups_dn = groups_dn

        # Check
        if admin_login and (not admin_password):
            raise ValueError('You must specify a password for the local admin')
        self.has_local_admin = (admin_login) and (admin_password)
        if (not self.server) and (not self.has_local_admin):
            raise ValueError(
                'You must specify an LDAP server or a local admin')

        # Connect to LDAP server
        if self.server:
            self.ldap_connection = ldap.initialize(self.server)
            self.ldap_connection.simple_bind_s(self.ldap_admin_dn,
                                               self.ldap_password)
        else:
           self.ldap_connection = None

The Singleton is created in the server startup function (so before any request) and the subsequent requests just retrieve the instance. We use the admin account to login:

def main(global_config, **settings):

    # Create routes and so on    
    # Get configuration for LDAP connection from app.registry.settings

    # Create the singleton to connect to LDAP
    app.users_groups = UsersAndGroups(admin_login, admin_password, ldap_server,
                                      ldap_admin_dn, ldap_password, users_dn,
                                      groups_dn)

    return app

This works most of the time but sometimes LDAP operations fail with INSUFFICIENT_ACCESS errors. The error happens when we restart the server for instance when doing local development (in which case we use pserve with the reload option) but also on the production server (where the services are managed through circus and chaussette -- we usually launch several processes). The only solution we found is to shutdown the server and start it again.

We are looking for clues on what's happening and how to solve it.

The login and password are correct since it works most of the time. AFAIU a problem in bind should raise an exception during server startup. I was wondering if the autoreload or the multiple processes could trigger such problems (since it usually works when restarting the server).

Some version information:

  • OS: Ubuntu 12.04 or 14.04
  • python: 2.7
  • OpenLDAP: 2.4.28
  • python_ldap: 2.4.25

Aucun commentaire:

Enregistrer un commentaire