Tuesday, September 15, 2009

Authenticating against an Active Directory Server, pt. 1


The most recent issue I am looking at involves authenticating web applications against Active Directory. I have not yet started on the Kerberos-style authentication, although that is on the high-priority list since it would allow single-sign-on from desktop clients. At the moment, however, I am trying to support simple LDAP authentication against the AD servers.

LDAP authentication involves opening a connection to a LDAP server, locating the distinguished name of the user to be authenticated, and binding the LDAP connection using the user's distinguished name and password. Since an Active Directory server is essentially a LDAP server, the basic procedure is the same, with a few minor changes. The first change is the second step, locating the user's distinguished name, which is not needed when hitting AD. In fact, it is not possible with AD, since AD does not allow anonymous connections (as far as I currently know); locating the user's distinguished name would require previously binding the connection with a known account. The second change is step zero, locating the AD server.

Locating an Active Directory server is itself a multiple step process:

  1. The client should query DNS for SRV records matching _ldap._tcp.fully-qualified-domain-name. For example, if our AD domain were MCB, and our domain were example.com, a search for SRV records matching the name _ldap._tcp.mcb.example.com would return, say, 30 SRV records providing hostnames of AD LDAP servers. (In normal, helpful DNS style, it would also probably give their A records, making a second query potentially unnecessary.) The SRV records include the port number of the LDAP service, as well as a priority and weight value to aid in choosing a specific record. In our case, all of the priorities and weights are the same, making all of the records equivalent. However, on any given part of our network, not all of the servers are reachable, and not all of those that are reachable are local; further, the subset of reachable servers differs depending on network location.

  2. The client then sends a LDAP query over UDP to each of the servers and uses the server which responds to the query first. (This step is bizarre in so many ways. LDAP is defined over TCP, not UDP. Further, making a query to every server? On the other hand, the query is considerably constrained, so it should not be computationally expensive. On yet another hand, however, is the fact that there is supposed to be further negotiation to narrow the server down, in case the nearest one did not respond to the query fastest.) How Domain Controllers Are Located in Windows describes it this way:

    The [client] sends a datagram to the computers that registered the name. [...] For DNS domain names, the datagram is implemented as an LDAP User Datagram Protocol (UDP) search. (UDP is the connectionless datagram transport protocol that is part of the TCP/IP protocol suite. TCP is a connection-oriented transport protocol.) Each available domain controller responds to the datagram to indicate that it is currently operational....

    The LDAP ping query seems to be defined by the Microsoft Communication Protocols, 7.3.3 LDAP Ping. Subsections there describe the stylized query as well as successful and failing responses.

  3. The client either uses the AD server which responded to the query or uses the query response to locate a better server. (If I am a little sketchy on this step, it is because I have not yet studied the query results and because using the responding server itself seems the simple way out.)

Easy enough; the only real downside is that LDAP queries are defined as BER-encoded ASN.1.

Once the server is identified, the remaining actions are relatively simple: Establish a (TCP) LDAP connection and bind it with the user's information; if the binding succeeds, the user is authenticated. As I mentioned earlier, it is neither necessary nor possible to query the AD server for the user's distinguished name. Instead, the user's identity is provided by the user's account name appended to the domain. The account name is identified by the sAMAccountName property in the user's AD record; the identification string looks like "user@MCB.EXAMPLE.COM".

In order to prevent the user identifier and password from being sent across the network in the clear, AD servers should either support LDAPS (LDAP over an SSL connection) or the StartTLS option. Since only one port is available from the SRV records and it is the "normal" port 389, and since the AD servers do not respond to connections to the 636 LDAPS port, StartTLS is the way to go.

Hopefully, in part 2 I will present some actual Java code for doing the authentication.

Kohsuke Kawaguchi's blog on Hudson's Active Directory plugin was my first link on the trail. Further information can be found in Microsoft's Windows 2000 Server Domain Controller Location Process, Locating Active Directory Servers, and in the Help and Support How Domain Controllers Are Located in Windows.

No comments: