Book Read Free

LDAP System Administration

Page 27

by Gerald Carter


  database ldap

  This declaration tells slapd to acquire its data from another LDAP server, allowing it to act as a proxy for that server. If OpenLDAP complains that ldap is not a valid database type, verify that —enable-ldap and —enable-rewrite were actually used when compiling the server. Even though OpenLDAP will not store any actual data for this partition, slapd must still be given the naming context of the database (ou=windows,dc=plainjoe,dc=org) using the standard suffix paramete:.

  suffix ou=windows,dc=plainjoe,dc=org

  This is an arbitrary suffix; it does not correspond to the DN of users' container in Active Directory. The uri and suffixmassage parameters tell slapd about the target directory (the directory being proxied) and the request rewrite rules. Your server must replace the suffix ou=windows,dc=plainjoe,dc=org with cn=users,dc=ad,dc=plainjoe,dc=org before passing any request to the target server. If no rewriting should be performed, the suffixmassage directive can be omitted.

  uri ldap://ad.plainjoe.org/

  suffixmassage ou=windows,dc=plainjoe,dc=org

  cn=users,dc=ad,dc=plainjoe,dc=org

  The binddn and bindpw parameters provide a means of specifying the credentials to use when contacting the remote LDAP directory. Here you use a simple bind. If your proxy server and remote server existed on opposite sites on an insecure, or hostile, network, it would be prudent to modify the uri parameter to use LDAPS:

  ## Active Directory also allows the userPrincipalName value to be used in LDAP binds,

  ## so this could be ldap-proxy@ad.plainjoe.org.

  binddn cn=ldap-proxy,cn=users,dc=ad,dc=plainjoe,dc=org

  bindpw proxy-secret

  OpenLDAP's proxy code only provides a way to map attributes and object classes defined by its local schema to those stored in the target directory. The syntax for defining a mapping is:

  map attribute|objectclass [local_name|*] foreign_name|*

  A map must define whether it applies to an attribute or an objectclass. The name of the local attribute or object class is optional, but remote names are required. The asterisk (*) character can be used to match any name. Your proxy server should map Active Directory's sAMAccountName, name, and userPrincipalName attributes to the locally defined uid, cn, and mail attributes. You also need to map the local account object class to the target user object class. Here are the map statements that perform the mapping:

  ## Map these.

  map attribute uid sAMAccountName

  map attribute cn name

  map attribute mail userPrincipalName

  map objectclass account user

  The proxy server can filter out any remaining attributes by mapping any remaining remote attributes to nothing:

  ## Remove the rest.

  map attribute *

  To see the results of this mapping, compare the entry returned by querying Active Directory directly to the result obtained by going through the OpenLDAP proxy. Here's what happens when you query Active Directory; the items that will be provided by the proxy server are shown in bold:

  $ ldapsearch -H ldap://ad.plainjoe.org -x

  -D ldap-proxy@ad.plainjoe.org -w proxy-secret -x

  -b "cn=users,dc=ad,dc=plainjoe,dc=org" -LLL

  "(sAMAccountName=kristi)"

  dn: CN=Kristi Carter,CN=Users,DC=ad,DC=plainjoe,DC=org

  accountExpires: 9223372036854775807

  badPasswordTime: 0

  badPwdCount: 0

  codePage: 0

  cn: Kristi Carter

  countryCode: 0

  displayName: Kristi Carter

  givenName: Joe

  instanceType: 4

  lastLogoff: 0

  lastLogon: 0

  logonCount: 0

  distinguishedName: CN=Kristi Carter,CN=Users,DC=ad,DC=plainjoe,DC=org

  objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=ad,DC=plainjoe,DC=org

  objectClass: top

  objectClass: person

  objectClass: organizationalPerson

  objectClass: user

  objectGUID:: NDHKI8oYFkqN8da3Gl9a5Q= =

  objectSid:: AQUAAAAAAAUVAAAAEcNfczJiHypDFwoyUwQAAA= =

  primaryGroupID: 513

  pwdLastSet: 126784120014273696

  name: Kristi Carter

  sAMAccountName: kristi

  sAMAccountType: 805306368

  sn: Carter

  userAccountControl: 66048

  userPrincipalName: kristi@ad.plainjoe.org

  uSNChanged: 2963

  uSNCreated: 2957

  whenChanged: 20021006210839.0Z

  whenCreated: 20021006210637.0Z

  Now, issue a similar query to the proxy server—except that you'll look up a uid rather than an Active Directory sAMAccountName, and the root of your search will be the DN that you've assigned to the proxy. This time, the search can be done anonymously. Here's the result:

  $ ldapsearch -H ldap://ldap.plainjoe.org -x

  -b "ou=windows,dc=plainjoe,dc=org" -LLL "(uid=kristi)"

  dn: CN=Kristi Carter,ou=windows,dc=plainjoe,dc=org

  objectClass: top

  objectClass: person

  objectClass: organizationalPerson

  objectClass: account

  cn: Kristi Carter

  uid: kristi

  mail: kristi@ad.plainjoe.org

  When you compare the two results, you will see that:

  objectClass: user

  name: Kristi Carter

  sAMAccountName: kristi

  userPrincipalName: kristi@ad.plainjoe.org

  has been mapped to:

  objectClass: account

  cn: Kristi Carter

  uid: kristi

  mail: kristi@ad.plainjoe.org

  The proxy server returns something slightly different if you remove the directive that filters all the attributes that aren't explicitly mapped (map attribute *):

  $ ldapsearch -H ldap://ldap.plainjoe.org -x

  -b "ou=windows,dc=plainjoe,dc=org" -LLL "(uid=kristi)"

  dn: CN=Kristi Carter,ou=windows,dc=plainjoe,dc=org

  cn: Kristi Carter

  displayName: Kristi Carter

  mail: kristi@ad.plainjoe.org

  givenName: Kristi

  distinguishedName: CN=Kristi Carter,ou=windows,dc=plainjoe,dc=org

  objectClass: top

  objectClass: person

  objectClass: organizationalPerson

  objectClass: account

  cn: Kristi Carter

  uid: kristi

  sn: Carter

  While this query returns more information than the previous one, it is obvious that slapd is still filtering some of the attributes from the target entry. This filtering occurs because the attributes returned by the query are still controlled by the local schema defined in slapd.conf. If the OpenLDAP installation does not understand a given attribute or object class (for example, userAccountControl), and it has not been mapped to a known local schema item, the unknown value is filtered out.

  The LDAP proxy backend supports updating the target directory, should you require it. It also supports local ACLs in the LDAP database section; these ACLs can be used to control access to an LDAP proxy that presents a view of the company's internal directory services to external clients. The slapd-ldap(5) manpage has more details on both of these configuration possibilities.

  Push/Pull Agents for Directory Synchronization

  Push/pull agents are common tools for synchronizing information between directories. In this case, a single agent manually pulls information from one directory service and massages the data to make it acceptable for upload to another directory server. Several directory vendors provide synchronization agents of this type in the form of connectors and drivers. A connector transfers data from one directory to another (see Figure 9-8) using a common format, often XML-based, while a driver translates the connector's data format to something understood by the local directory.

  Figure 9-8. Using a connector/driver solution for synchronizing data among
different directory services

  A partial list of commercial connector/driver offerings includes:

  SunOne's XMLDAP (http://wwws.sun.com/software/products/directory_srvr/)

  Novell's DirXML (http://www.novell.com/products/edirectory/dirxml/)

  The advantage that most commercial connector/driver solutions enjoy over in-house solutions is an inherent knowledge of when data changes in the directory. This means that the directory can trigger the connector upon any relevant change; in most cases, an external agent can detect a change only by polling the directory.

  Despite this disadvantage, home-grown tools that act as middlemen between directory services can be very useful. The next chapter focuses on how to script directory operations using Perl and the Net::LDAP module.

  The Directory Services Markup Language

  The Extensible Markup Language (XML) has been hyped as the next big thing for several years now. Whether or not it has achieved its promise is a question I won't get into. LDAP has not been immune to the XML fever. The Directory Services Markup Language (DSML) is an XML schema for representing LDAP information using document fragments. DSML v1.0 could really only be described as an attempt to replace LDIF. With Version 2.0, however, released in May of 2002, DSML has grown up and gained some new and interesting functionality.[4]

  DSMLv2 is designed to provide methods for representing LDAP queries, updates, and the responses to these operations in XML. This means that it would be possible for small, embedded devices to access LDAP services without relying on an LDAP client library; they only need the ability to parse XML. Because XML-based standards such as SOAP will only become more prevalent over time, the Oasis Directory Service TC has included a description of how to embed DSML requests and responses into SOAP messages.

  It's too early to provide concrete examples of how to use this technology. Version 1.0 is only mildly interesting, and Version 2.0 of DSML is still in the early adopter phase. DSMLv2 will probably be accepted by the LDAP marketplace. Sun Microsystems will include native support for the specification in the next release of its SunOne Directory Server (Version 5.2). Microsoft is also developing a DSML development kit; this product is currently available as a beta release. Novell has also been very active in the development of DSML. All three companies have members serving on the DSML technical committee.

  * * *

  [4] The latest information about DSML can be found at the OASIS Directory Services Technical Committee's web page (http://www.oasis-open.org/committees/dsml/).

  Chapter 10. Net::LDAP and Perl

  No book on system administration is complete without some coverage of scripting. For many administrators, the scripting language of choice is Perl. Perl is very good at dealing with text files (such as LDIF files), and many third-party modules make it easy to accomplish complex tasks.[1]

  This chapter doesn't cover the basics of Perl programming. I assume that you are already comfortable with the language and its fundamental concepts, such as regular expressions, but none of the examples will require the help of a Perl guru for interpretation. Note that the scripts in this chapter are generally lax about conventions used in production Perl code, such as the use strict pragma and variable scoping (e.g., my or local).

  The Net::LDAP Module

  Two widely distributed Perl modules make it easy to write scripts that interact with an LDAP directory. One of these is the PerLDAP module, written by Leif Hedstrom from Netscape Communications (http://www.mozilla.org/directory/perldap.html). However, the last version was released in October of 2000.

  A more active project, and the module that I discuss in this chapter, is Graham Barr's perl-ldap module (often referred to as Net::LDAP). The examples in this chapter are based on Version 0.26 of this module. The module's home is located at http://perl-ldap.sourceforge.net/, but it's simpler to get it through the Comprehensive Perl Archive Network (CPAN) at http://search.cpan.org. Before you install Net::LDAP, make sure that the following modules are present:

  URI

  If you want to parse ldap:// URIs

  Digest::MD5

  For Base64 encoding

  IO::Socket::SSL

  For LDAPS and StartTLS support

  XML::Parser

  To read and write DSML files

  Authen::SASL

  For SASL authentication support

  All of these modules (and any of their requisite modules) can be downloaded from CPAN mirrors. As a convenience, several of these modules have been packaged into a single module named Bundle::Net::LDAP, which can also be download from CPAN.

  * * *

  Tip

  One of the easiest ways to ensure that all dependencies for a Perl module are met is to use the interactive shell provided by Andreas Koenig's CPAN module. After downloading and installing this module from http://search.cpan.org/search?dist=CPAN, you can learn about its features by executing the command perldoc CPAN.

  * * *

  Programming with the Net::LDAP module is not tricky. You can discover a lot about it by typing the command perldoc Net::LDAP at a shell prompt; additional documentation can be found under Net::LDAP::Examples and Net::LDAP::FAQ.

  * * *

  [1] For more information on Perl, visit the O'Reilly Perl web site at http://www.perl.com/ or the Perl Monger's web site at http://www.perl.org/. If you're new to Perl, I recommend Learning Perl, by Randall Schwartz and Tom Phoenix (O'Reilly) and Programming Perl, by Larry Wall, Tom Christiansen, and Randall Schwartz (O'Reilly).

  Connecting, Binding, and Searching

  To get started with the Net::LDAP module, we will write a basic LDAP query script named search.pl . This script illustrates the methods used to connect to a directory server and retrieve information. It begins by importing the Net::LDAP symbols via the use pragma:

  #!/usr/bin/perl

  use Net::LDAP;

  After the module has been included, you can create a new instance of a Net::LDAP object. To create a new Net::LDAP instance, you need the hostname of the LDAP server to which the script should connect. The constructor allows several optional arguments, of which the most common and useful are:

  port

  The TCP port on which the directory server is listening. If this parameter is not defined, it defaults to the well-known LDAP port (389).

  version

  The LDAP version to be used when connecting to the server. The default is Version 2 in the 0.26 release. However, this is likely to change in the future. Always explicitly set the version parameter if your Perl program replies with LDAPv3 features (such as SASL or referrals).

  timeout

  The time in seconds that the module should wait when contacting the directory server. The default value of 120 seconds is sufficient for most situations, but for more complex searches or when communicating with a very large directory, it may be necessary to increase this value.

  The next line of code establishes a connection to the host ldap.plainjoe.org on port 389 using Version 3 of the protocol. The returned value is a handle to a Net::LDAP object that can be used to retrieve and modify data in the directory.

  $ldap = Net::LDAP->new ("ldap.plainjoe.org", port =>389,

  version => 3 );

  The script can bind to the directory after it obtains a handle to the LDAP server. By default, Net::LDAP uses an implicit anonymous bind, but it supports all the standard binds defined by the LDAPv3 RFCs (anonymous, simple, and SASL). For now, we only examine how to use a simple bind.

  However, before binding to the server, call start_tls( ) to encrypt the connection; you don't want to send the user DN and password across the network in clear text. In its simplest form, the start_tls( ) method requires no parameters and appears as:

  $ldap->start_tls( );

  * * *

  Checking for Errors

  Most of the Net::LDAP methods return an object with two methods for obtaining the function's return status. The code( ) method retrieves the integer return value from the method call that created the object, and
the error( ) method returns a descriptive character string associated with the numeric code. The constants for the various LDAP errors are contained in the Net::LDAP::Constant module. Specific error codes can be included in your code by adding a line similar to the following one:

  use Net::LDAP::Constant qw(LDAP_SUCCESS);

  The following code tests for an error condition after some arbitrary LDAP call:

  if ($result->code( ) != LDAP_SUCCESS) {

  die $result->error( );

  }

  Because most methods indicate success with a return code of zero, this error check can be shortened to:

  die $result->error( ) if $result->code( );

  The Net::LDAP::Util module contains a few extra functions for obtaining more error information. The ldap_error_text function returns the descriptive POD text for the error code passed in as a parameter, and ldap_error_name returns the constant name for an integer (for example, if it is passed the integer 0, it returns the string LDAP_SUCCESS).

  * * *

  It is a good idea to check for errors after attempting to establish a secure communication channel; if start_tls( ) fails, and the script continues blindly, it might inadvertently transmit sensitive account information in the clear. To do so, save the result object returned by start_tls( ), and then use the code( ) method to find out whether start_tls( ) succeeded:

  $result = $ldap->start_tls( );

  die $result->error( ) if $result->code( );

  If the script tries to establish transport layer security with a server that does not support this extended operation, the error check displays an error message and exits:

  Operations error at ./search.pl line XXX.

  The actual error from Net::LDAP::Constant is LDAP_OPERATIONS_ERROR .

  Now you can safely send the sensitive data to the server. A simple authenticated bind requires only a DN and a password. If neither are provided, the call attempts to establish an explicit anonymous binding (as opposed to the implicit bind used when bind( ) is not called at all). The following line seeks to bind your client to the directory as the entry cn=Gerald Carter,ou=people,dc=plainjoe,dc=org using the password hello. Once again, you use error( ) and code( ) to check the return status:

 

‹ Prev