Chapter 13. Writing a Simple LDAP Proxy

Table of Contents
13.1. Connection Pooling
13.2. Load Balancing & Failover
13.3. Listening For & Handling Client Connections
13.4. DN & Attribute Rewriting

The OpenDJ LDAP SDK example Proxy demonstrates a simple LDAP proxy that forwards requests to one or more remote directory servers. Although the implementation is intended as an example, it does demonstrate use of the asynchronous API, load balancing, and connection pooling.

The Proxy example sets up connections pools with load balancing to the directory servers. It passes the connection factories to a ProxyBackend that handles the requests passed back to the directory servers. It also sets up an LDAP listener to receive incoming connections from clients of the Proxy.

The ProxyBackend uses separate connection factories, one for bind operations, the other for other operations. It uses the proxied authorization control to ensure operations are performed using the bind identity for the operation.

The ProxyBackend's function is to handle each client request, encapsulating the result handlers that allow it to deal with each basic operation. It authenticates to the directory server to check incoming credentials, and adds the proxied authorization control to requests other than binds. The ProxyBackend handles all operations using asynchronous connections and methods.

13.1. Connection Pooling

As shown in the Proxy example, the Connections.newFixedConnectionPool() returns a connection pool of the maximum size you specify.

final List<ConnectionFactory> factories = new LinkedList<~>();

factories.add(Connections.newFixedConnectionPool(Connections
        .newAuthenticatedConnectionFactory(Connections
                .newHeartBeatConnectionFactory(new LDAPConnectionFactory(
                        remoteAddress, remotePort)),
                        Requests.newSimpleBindRequest(proxyDN,
                                proxyPassword.toCharArray())),
                                Integer.MAX_VALUE));

Connections are returned to the pool when you close() them. Notice that Connections also provides methods to return ConnectionFactorys with a heart beat check on connections provided by the factory, and connection factories that authenticate connections before returning them.

Connections in the pool are intended for reuse. The Proxy gets an authenticated connection, which is a connection where the OpenDJ LDAP SDK passes a bind request immediately when getting the connection. The Proxy then uses proxied authorization to handle the identity from the client requesting the operation. As a rule, either handle binds separately and use proxied authorization as in the Proxy example, or else make sure that the first operation on a connection retrieved from the pool is a bind that correctly authenticates the user currently served by the connection.

When you close() a connection from the pool, the OpenDJ LDAP SDK does not perform an unbind(). This is why you must be careful about how you manage authentication on connections from a pool.

13.2. Load Balancing & Failover

The Connections.newLoadBalancer() method returns a load balancer based on the algorithm you choose. Algorithms include both round robin for equitably sharing load across local directory servers, and also failover usually used for switching automatically from an unresponsive server group to an alternative server group. The algorithms take collections of connection factories, such as those that you set up for connection pooling.

The following excerpt shows how to set up round robin load balancing across directory servers.

final List<ConnectionFactory> factories = new LinkedList<ConnectionFactory>();

// Set up a ConnectionFactory for each directory server in the pool as shown in
// the previous example, and then set up a load balancer.

final RoundRobinLoadBalancingAlgorithm algorithm =
        new RoundRobinLoadBalancingAlgorithm(factories);

final ConnectionFactory factory = Connections.newLoadBalancer(algorithm);

With multiple pools of directory servers, for example in a deployment across multiple data centers, also use fail over load balancing. Fail over load balancing directs all requests to the first (preferred) pool of servers until problems are encountered with the connections to that pool. Then it fails over to the next pool in the list. Therefore in each data center you can set up round robin load balancing, and then set up fail over load balancing across data centers.

// localFactory:  ConnectionFactory to servers in the local data center
// remoteFactory: ConnectionFactory for servers in a remote data center
// localFactory and remoteFactory use round robin load balancing "internally".

final List<ConnectionFactory> factories =
        Arrays.asList(localFactory, remoteFactory);

final FailoverLoadBalancingAlgorithm algorithm =
        new FailoverLoadBalancingAlgorithm(factories);

final ConnectionFactory factory = Connections.newLoadBalancer(algorithm);

The algorithms also include constructors that let you adjust timeouts and so forth.

13.3. Listening For & Handling Client Connections

You create an LDAPListener to handle incoming client connections. The LDAPListener takes a connection handler that deals with the connections, in this case connections back to the directory servers handling client requests.

final LDAPListenerOptions options = new LDAPListenerOptions().setBacklog(4096);
LDAPListener listener = null;
try {
    listener = new LDAPListener(localAddress, localPort, connectionHandler,
            options);
    System.out.println("Press any key to stop the server...");
    System.in.read();
} catch (final IOException e) {
    System.out.println("Error listening on " + localAddress + ":" + localPort);
    e.printStackTrace();
} finally {
    if (listener != null) {
        listener.close();
    }
}

You get a ServerConnectionFactory to handle requests coming from clients. The ServerConnectionFactory takes a request handler that deals with the incoming client requests. The request handler implements handlers for all supported operations. The Proxy example implements a ProxyBackend to handle requests. The ProxyBackend sends the requests on to the backend directory servers and routes the results returned back to client applications.

final ProxyBackend backend = new ProxyBackend(factory, bindFactory);
final ServerConnectionFactory<LDAPClientContext, Integer> connectionHandler =
        Connections.newServerConnectionFactory(backend);

See the Proxy example code for details about the ProxyBackend implementation.

13.4. DN & Attribute Rewriting

Suppose you have a client application that expects a different attribute name, such as fullname for a standard attribute like cn (common name), and that expects a distinguished name (DN) suffix different from what is stored in the directory. If you cannot change the application, one possible alternative is a proxy layer that does DN and attribute rewriting.[9]

# A search accessing the directory server
$ ldapsearch -b dc=example,dc=com -p 1389 "(cn=Babs Jensen)" cn
dn: uid=bjensen,ou=People,dc=example,dc=com
cn: Barbara Jensen
cn: Babs Jensen

# The same search search accessing a proxy that rewrites requests and responses
$ ldapsearch -b o=example -p 8389 "(fullname=Babs Jensen)" fullname
dn: uid=bjensen,ou=People,o=example
fullname: Barbara Jensen
fullname: Babs Jensen

The OpenDJ LDAP SDK RewriterProxy example builds on the Proxy example, rewriting requests and search result entries. When you read the example, look for the rewrite() methods.

In the above output, the rewriter proxy listens on port 8389, connecting to a directory server listening on 1389. The directory server contains data from Example.ldif.



[9] Some servers, such as OpenDJ directory server, can do attribute rewriting without a proxy layer. See your directory server's documentation for details.