LDAP Proxy

Your software needs to authenticate using the LDAP protocol.  You have configured it to talk to the central IT directory service, but something has gone awry.  Your client logs are only telling you half the story, and to figure out what’s going on, you need logs from the directory.  But those logs are unavailable or non-existent.  What to do?

Enter the LDAP proxy.  This software sits between your client and the actual directory.  You configure your client to speak to the proxy.  The proxy passes the requests on to the directory, collects the responses, and forwards them back to your client.  It can also log a record of the requests and responses so you can puzzle out what the issue is.

To many people, the prospect of setting up such a proxy can seem daunting.  It is actually quite simple.  Here are the high-level steps:

  1. Make sure you have a modern Python of the 2.x variety.  Python 2.7 is what you should be using, but you may be able to squeak by with Python 2.6.
  2. Create a Python virtualenv.  You don’t want to pollute your OS Python’s site-packages with random modules.  Using virtualenv is always a great way to start a project.
  3. Activate your virtualenv and use pip to install Twisted.  This is an awesome Python module used for programming asynchronous network services.
  4. Use pip again to install the latest, bleeding edge Ldaptor.  The last release (14.0 at the time of this writing) does not have the ProxyBase class required to make scripting the proxy simple.  You can pull the source from GitHub, activate your virtualenv, and run `pip install .` from within the folder.  See this Stack Overflow answer for more info on some of the ways to use pip to install Python modules.

Save the following script as ldap_proxy.py:

from ldaptor.protocols import pureldap
from ldaptor.protocols.ldap.ldapclient import LDAPClient
from ldaptor.protocols.ldap.ldapconnector import connectToLDAPEndpoint
from ldaptor.protocols.ldap.proxybase import ProxyBase
from twisted.application.service import Application, Service
from twisted.internet import defer, protocol, reactor
from twisted.internet.endpoints import serverFromString
from twisted.python import log
from functools import partial

class LoggingProxy(ProxyBase):
"""
A simple example of using `ProxyBase` to log requests and responses.
"""
    def handleProxiedResponse(self, response, request, controls):
        """
        Log the representation of the responses received.
        """
        log.msg("Request => " + repr(request))
        log.msg("Response => " + repr(response))
        return defer.succeed(response)


    def ldapBindRequestRepr(self):
        l=[]
        l.append('version={0}'.format(self.version))
        l.append('dn={0}'.format(repr(self.dn)))
        l.append('auth=****')
        if self.tag != self.__class__.tag:
            l.append('tag={0}'.format(self.tag))
        l.append('sasl={0}'.format(repr(self.sasl)))
        return self.__class__.__name__+'('+', '.join(l)+')'

pureldap.LDAPBindRequest.__repr__ = ldapBindRequestRepr


class LoggingProxyService(Service):
    endpointStr = "tcp:10389"
    proxiedEndpointStr = 'tcp:host=localhost:port=8080'

    def startService(self):
        factory = protocol.ServerFactory()
        use_tls = False
        clientConnector = partial(
            connectToLDAPEndpoint,
            reactor,
            self.proxiedEndpointStr,
            LDAPClient)

        def buildProtocol():
            proto = LoggingProxy()
            proto.clientConnector = clientConnector
            proto.use_tls = use_tls
            return proto

        factory.protocol = buildProtocol
        ep = serverFromString(reactor, self.endpointStr)
        d = ep.listen(factory)
        d.addCallback(self.setListeningPort)
        d.addErrback(log.err)

    def setListeningPort(self, port):
        self.port_ = port

    def stopService(self):
        # If there are asynchronous cleanup tasks that need to
        # be performed, add deferreds for them to `async_tasks`.
        async_tasks = []
        if self.port_ is not None:
            async_tasks.append(self.port_.stopListening())
        if len(async_tasks) > 0:
            return defer.DeferredList(async_tasks, consumeErrors=True)


application = Application("Logging LDAP Proxy")
service = LoggingProxyService()
service.setServiceParent(application)


Now run the script with:


$ twistd -ny ldap_proxy.py

You can modify the variables endpointStr and proxiedEndpointStr in order to change the host and/or port of the proxied directory and the proxy.

 

Leave a comment