There seems to be a shortage of howto
documents telling how to
actually set up LDAP. I'm doing this, and I'm documenting the procedure.
Web links:
LDAP is a directory service
. Originally, telephone landline
operating companies provided a service, accessible online via your landline,
whereby you could give the Distinguished Name of someone, i.e. his Common Name
and locality, and the human operator would read back his phone number. This
directory service in digital form was codified in ITU X.500. However,
implementers had complaints that X.500 was too complicated and used too much
network bandwidth, and also required the OSX network protocl stack which was a
non-starter, and so a subset was defined and implemented (on TCP/IP), under the
name Lightweight Directory Access Protocol
(LDAP) (RFC 4510).
The X.500 Directory Information Tree
(DIT) is theoretically global
but is supposed to be rooted in national root servers. In practice for LDAP,
the maintainer of each server creates a plausible suffix
for his own
branch of the DIT and uses it without coordinating with regional or national
authorities. For UCLA-Mathnet the suffix is dc=math,dc=ucla,dc=edu.
The objects sent out by a LDAP server are called entries
. Each has a
Distinguished Name (attribute code is dn
) and arbitrarily many
additional attributes as specified by a schema
. Same-named attributes
may appear more than once per entry, and OpenLDAP has an ad-hoc extension to
keep them in a specific order. The distinguished name must be unique within
the DIT sent out by a LDAP server, and is supposed to be unique globally. It
consists of a comma separated list of key=value pairs where the keys are
attributes in DIT entries. (It is not clear whether values may contain commas
or spaces; they certainly can in X.500 Distinguished Names.) The distinguished
name in LDAP is little-endian: the relative distinguished name
(RDN)
specific to the referent comes first, and the country code or equivalent
toplevel unit comes last. (The order is big-endian in X.500.) For example,
this is the Distinguished Name we are using for UCLA-Mathnet's webserver, in
little-endian order:
cn=arachne.math.ucla.edu,ou=Hosts,dc=math,dc=ucla,dc=edu
dn is interpreted semantically as Distinguished Name
,
cn is Common Name
, ou is Organizational Unit
,
and dc is Domain Component
.
In this project I am setting up LDAP on both my work nets and at home, but the requirements are fairly similar in both contexts.
Currently at work we use Sun Microsystems' NIS (formerly known as YP). Thus we have a fully populated, though obsolete, directory service. We want LDAP to replace NIS, with minimum disruption to operations and to the users. (The very good reasons to junk NIS are not covered in this document.)
Our administrative tools currently write into UNIX flat files such as /etc/master/passwd; thus we have a (locally written) program to propagate changes in both directions between the flat files and LDAP. Much better would be to rewrite the administrative tools to use LDAP directly. We need, but do not yet have, a daemon sensitive to iNotify on the flat files which will copy changes to LDAP immediately after the files are edited.
We want (and have) one directory server per subnet which can keep the subnet functioning even if it is cut off by a network failure. When my home laptop is away from home it needs to continue to function despite not being able to connect to the home net. Thus, replication is required.
The work nets each have several hundred users; the home net is of course much smaller. Resources required to support LDAP at this scale are expected to be minimal.
We need a procedure to back up the LDAP configuration and content, so after a machine fails, service can be restored promptly and without a lot of hassle for the system administrators.
Microsoft Windows Active Directory cohabits the work net. We have a long term goal to coordinate between UNIX and Windows, but progress on that goal is not part of this document. A working LDAP on UNIX is a prerequisite for that project.
I am installing OpenLDAP version 2.4, package openldap2 from OpenSuSE v11.4. Out of the box, it needs these files to be edited:
/etc/sasl2/slapd.conf -- You need to edit your mechanism list; for example we added GSSAPI.
/etc/pam.d/slapd -- If you're going to use SASL authentication
(versus simple
authentication which directly checks the
client's password in the LDAP database itself), and in the likely
case that your saslauthd is set to use PAM, you need a PAM script
named slapd. For us it's a symbolic link to a generic script that
we use for all services where the user does not start a shell session.
/etc/openldap/slapd.conf -- We are using runtime configuration, so slapd.conf needs to be modified to make this happen when the daemon is started the first time, and then removed or renamed. See the next section for details. slapd.conf contains the LDAP root password, and hence must not be world-readable.
/etc/openldap/slapd.d -- This is a directory which would contain the configuration database; however, I think all the database items should be in the database directory, which in the stock configuration is /var/lib/ldap , so I've moved slapd.d there and made a symlink. slapd.d contains the same secrets as slapd.conf, so it and its contents needs similar permissions. ldap:ldap mode 600 or 700 is satisfactory. As far as I can see, there is nothing that actually uses the group permission.
/var/lib/ldap/DB_CONFIG -- I stuck with the SuSE-provided values, which give you a cache size of 15Mb (my whole database takes 24Mb including overhead) and which automatically removes old transaction logs. If you have hundreds of users you may need to make some of these items bigger.
/etc/sysconfig/openldap --
It's not clear where this file actually came from!
/etc/init.d/ldap -- needs some hacks.
find $1 …to
find $1/. …so as to follow the symlink that we use.
Before starting setup, you need to prepare some symbol space objects.
Name of the LDAP superuser. This probably should not be the same as
your UNIX root user due to hassle with password changes. I'm using
ldaproot
followed by the suffix of each database, e.g.
uid=ldaproot,dc=math,dc=ucla,dc=edu. It is not necessary that any LDAP
entry actually exist and have this Distinguished Name, because
checking the RootDN and RootPW is handled as a special case.
Are we going to have a separate user for replication? If so (see
below), I'm going to call it ldaprepl
. It needs permission on
the master server to read every entry, and slave servers need to have
its password in clear text in their configuration databases so they can
authenticate and read any entry to replicate it. This is an ordinary
user and there has to be an actual posixAccount object with this name
and password, which has to be on the master (one of them) before any
slave can use it as a source of replication. However, it will then be
on the slaves, which can then thereafter function as masters in a
multi-master configuration.
Passwords for each of these, stored in a file protected by UNIX file permissions: owned by root, mode 600. Nss_ldap.so looks for ldaproot's password in /etc/openldap/root.secret; the name is a compile-time option. /etc/ldap.secret should be a symbolic link to /etc/openldap/root.secret. I'm using /etc/openldap/repl.secret for ldaprepl's password.
You never have to type these in, so you should make them strong, i.e. long. I suggest 20 truly random printable bytes (or more) giving 130 bits of strength at 6.5 bits/byte (approximately). I'm suggesting that they be printable so you can look at them and check which you have, and to avoid waking sleeping dragons. Beware, the file content should not have a newline; if there is one it becomes part of the password and could never be typed in if you needed to.
If you don't have a random password generator, here's a command line to generate one (without a newline). The length is random but is about 3/4 of the number of bytes in the initial step. The final deletion step removes control characters, space, single and double quote, dollar sign, backslash, grave, and DEL. Execute as root so you will be allowed to write the output file.
openssl rand 30 | tr '\200-\377' '\0-\177' | tr -d '\0-\040\042\044\047\134\140\177' > /etc/openldap/root.secret chmod 600 /etc/openldap/root.secret
The passwords are also needed in hashed form. The SSHA algo (SHA-1 with seed) is the default, though it's legal to use other algos. Use this command line. The seed, and thus the result, will be different every time you run it. The -n option suppresses the ending newline.
slappasswd -T root.secret -n -h '{SSHA}'
When you use ldapmodify it prefers complex text strings in base64 encoded form. Here is how to produce it. base64 values often end with '='; don't lose it. Remember the -n option (no newline) of echo.
echo -n "{SSHA}WouldntYouLikeToKnow" | base64
It's recommended to use hashed passwords whenever possible, but for testing you may find it useful to prefix the plaintext password with '{CLEARTEXT}' as an algo designator. For clarity in these examples I have specified a cleartext password without base64 encoding, but you need to remember to replace it with your own hashed password, usually base64 encoded (with a double colon after olcRootPW).
What are the security implications of using the LDAP superuser for replication versus a separate user?
The slave server has to authenticate, and so has to give a credential which is in plain text in its configuration database. Any competent system administrator sees a big red flag whenever a plaintext password is mentioned.
However, a different credential stored in a file, such as the unencrypted secret key for a RSA public key, or a Kerberos keytab with its symmetric key, differs only in not being printable. I'm not going to explore alternate file-based credentials for use with LDAP, even though GSSAPI (Kerberos) probably could be made to work.
It is useless to put the relevant files on an encrypted filesystem because it has to be mounted (with the password given) and readable any time LDAP is to be used. The encrypted filesystem is useful in defense if the media is physically stolen when not mounted, but is irrelevant against a root exploit on a running system.
A non-file-based credential such as a smart card ultimately is accessed via a file-like interface, in this case a device inode, protected by UNIX file permissions, and hence is no better.
It is outrageous to require the sysadmin to type in the password every time replication is to occur. If a key agent is used, again its service socket is protected by UNIX file permissions, and is no better than the flat file. Even in the unlikely case that authentication can be limited to boot time, the sysadmin would have to be physically present with the machine when it's rebooted in the middle of the night, and the password would then be in memory, protected by UNIX file permissions. A humanly originated credential is not going to fly.
The software (nss_ldap.so) required when a user logs in expects to read the superuser password from /etc/openldap/root.secret. This name is a compile-time option.
Thus, we either roll over for the plaintext password in a file, or don't use LDAP.
Now, are we going to use the LDAP superuser or a separate user for replication, putting this user's password in the LDAP configuration database?
The replication user can read any entry, exposing users' hashed passwords for offline dictionary attacks and possibly revealing other data that is not public record. The superuser can do the same and also write any entry, exposing us to vandalism. But in terms of earning money from a root exploit, the way to profit is by compromising accounts, that is, read access is the important one, and so the extent of vulnerability doesn't help us decide whether to use a separate user.
The configuration database, where the slave server keeps the password in plain text, is in files protected by UNIX file permissions and readable only by the LDAP special user (and UNIX root). Via LDAP this database is explicitly unreadable by every user (except the LDAP superuser). Thus, it is protected as well (or as poorly) as the required flat file that contains the password.
It appears to be feasible to have a separate user for replication, but it looks to me that the security improvement from doing so is minimal. Therefore I have decided to punt on this: to use the LDAP superuser for replication and to allow its password to be present in the configuration database in plain text.
SuSE's openldap2 by default relies on slapd.conf whereas I want to use
runtime configuration. The easiest way to make that happen is to create a
seed
slapd.conf and convert it. Alternatively, if you have a working
LDAP database you can copy it to another machine and it will work, but there
is a chicken and egg issue here.
The initial file content goes like this (jimc's creation). Note: in this document I'm trying to show how to use runtime configuration maximally. There are several items below which could profitably be added to this configuration and converted. Update: In particular, I decided that using LDIF to load schemas, at the present stage of development, is just asking for trouble, so on a re-installation I included the schemas in slapd.conf.
Include kerberos.schema in the likely case that you are going to store
your Kerberos KDC database in LDAP. See
Kerberos Database in LDAP
for details.
Warning! This file contains the LDAP superuser's password hash, and so it should have restrictive permissions. The ldap user has to read it, so the recommended permission is ldap 600.
# Minimal slapd.conf to create cn=config. # By jimc@math.ucla.edu, 2012-02-11 # Ends up in /var/lib/ldap/slapd.d/cn\=config.ldif pidfile /var/run/slapd/slapd.pid argsfile /var/run/slapd/slapd.args TLSCertificateFile /etc/ssl/hostcerts/host.crt TLSCertificateKeyFile /etc/ssl/private/host.key TLSCACertificatePath /etc/ssl/certs # Ends up in /var/lib/ldap/slapd.d/cn\=config/cn\=module\{0\}.ldif modulepath /usr/lib/openldap/modules moduleload back_hdb.la moduleload back_ldif.la moduleload back_monitor.la # Ends up in /var/lib/ldap/slapd.d/cn\=config/olcDatabase\={0}config.ldif # Does not need a suffix. database config rootdn "cn=config" rootpw "{SSHA}WouldntYouLikeToKnow" # Schemas, in order include /etc/openldap/schema/core.schema include /etc/openldap/schema/cosine.schema include /etc/openldap/schema/inetorgperson.schema include /etc/openldap/schema/rfc2307bis.schema include /etc/openldap/schema/yast.schema include /etc/openldap/schema/misc.schema include /etc/openldap/schema/mathnet.schema include /etc/openldap/schema/kerberos.schema
Now make sure that slapd is not running by accident, and execute this command line; append -u to test but not convert. It's essential that the files created be read-write for the ldap user, so slapd can read them. The -F directory has to already exist, owned by ldap, mode 700.
As distributed, slapd expects to find the dynamic configuration in /etc/openldap/slapd.d; this is probably a compiled-in default for various utilities. But mutable files properly are located in /var. Thus I have created /var/lib/ldap/slapd.d and made a symbolic link to it from /etc/openldap/slapd.conf .
mkdir /var/lib/ldap/slapd.d
ln -s /var/lib/ldap/slapd.d /etc/openldap/slapd.d
su -c "/usr/sbin/slaptest -f /etc/openldap/slapd.conf -F /etc/openldap/slapd.d -v " ldap
You now have a cn=config (runtime configurable) LDAP database with no content. Since we're doing runtime configuration, start slapd by:
/etc/init.d/ldap start
First test if you can connect to your slapd. During initial setup, it's going to be safest if you use the ldapi (UNIX socket) URI as shown. Do the command as root so you can read the secret.
ldapsearch -x -D cn=config -y root.secret -H ldapi:/// -b cn=config
Suggestion: redirect the output to a file because it dumps everything including the hardwired schemas.
Note on LDAPI: while OpenLDAP is compiled with a correct default for the pathname of the LDAPI socket, other packages cannot discover it, and so you will need to specify the pathname, URL encoded (i.e. slashes changed to %2f). Here is the correct value on a SuSE system:
ldapi://%2fvar%2frun%2fslapd%2fldapi/
Oops! I can't connect, invalid credentials (error 49). The reason is that I forgot to replace the sample password with the actual one in the slapd.conf.seed file. To fix, shut off slapd (important, do /etc/init.d/ldap stop), and edit /var/lib/ldap/slapd.d/cn\=config/olcDatabase\=\{0\}config.ldif replacing the value of olcRootPW:: (2 colons mean a base64 encoded value). Restart slapd. Now I can connect and see everything that slapd knows so far.
Here's a modified test command which only shows the root DN and password. (The frontend database is shown too but it doesn't have any root DN.)
ldapsearch -x -D cn=config -y /etc/openldap/root.secret -H ldapi:/// -LLL -b cn=config '(objectClass=olcDatabaseConfig)' olcDatabase olcRootDN olcRootPW
Here is a description of the schemas that I added (order is significant). The sources are in /etc/openldap/schema/ .
core.schema -- The basic object types that you have got to have, from RFC 2252, 2256, and several others.
cosine.schema -- Attributes of varying usefulness from RFC 1274 and/or imported from X.500.
inetorgperson.schema -- Attributes of a person
from
RFC 2798.
rfc2307bis.schema -- Defines the objectClasses and attributes needed to represent the normal UNIX directory files such as /etc/passwd, /etc/hosts and /etc/services. Not including /etc/aliases.
yast.schema -- I'm not entirely sure what this is used for, but of course on a SuSE system it should be included.
misc.schema -- The main item here is a representation for /etc/aliases.
mathnet.schema -- Declarations for the udata and hostgroup maps which are special creations at UCLA-Mathnet.
kerberos.schema -- If you are going to put your Kerberos database in LDAP, it would be a good idea to include the schema now rather than appending it later.
I initially loaded the schemas successfully using dynamic configuration, but since you need to do the slaptest thing to convert the schemas to LDIF format anyway, I decided that it was more sane to include schema loading in the initial slapd.conf.seed. Beware, it is not possible to delete or alter a schema by dynamic configuration; you will give slapd a segfault if you try. Only cowboy programming will succeed. You need to shut down your slapd and edit the configuration database by hand.
These instructions are kept for historical reference. I used them
successfully but they are superceded by include *.schema
in
slapd.conf.seed.
If you already have a working slapd configuration using runtime configuration (cn=config) with the schemas you want, you can just steal them; copy them someplace on the new machine. Look in /var/lib/ldap/slapd.d/cn=config/cn=schema/*.ldif of the old one.
If you don't have the LDIF files, you can create them by once again converting a slapd.conf file. Let's use /tmp/schemas.conf as its name; it would say just:
include /etc/openldap/schema/core.schema
include /etc/openldap/schema/cosine.schema
include etc. etc.
Now use this command line, where I'm using /tmp/schemas.d for the output:
mkdir /tmp/schemas.dAnd then the LDIF files would appear in /tmp/schemas.conf//cn=config/cn=schema/*.ldif . You may want to save them somewhere permanent for the next time you do this, like /etc/openldap/schemas/*.ldif .
chown ldap /tmp/schemas.d
su -c "/usr/sbin/slaptest -f /tmp/schemas.conf -F /tmp/schemas.d -v " ldap
Wherever you stole the LDIF files from, edit your copies to remove the operational attributes. The running slapd does not want to see them; it wants to use the current values that it computes. In mine they all come at the end of the file, with these names: structuralObjectClass entryUUID creatorsName createTimestamp entryCSN modifiersName modifyTimestamp .
Also edit the Distinguished Name to be added (the first line starting with
dn:
). The stolen copy will begin like this:
dn: cn={0}core objectClass: olcSchemaConfig cn: {0}core
It needs to change to be like this.
Remove the bracketed ordering numbers in dn: and in cn:,
and to the dn append the suffix ,cn=schema,cn=config
. The result
should look like:
dn: cn=core,cn=schema,cn=config objectClass: olcSchemaConfig cn: core
If you forget, you will get an
error messsage Server is unwilling to perform (53) additional info: no
global superior knowledge
. It's telling you that it doesn't know where
to put the new object in the tree, because the suffix is missing.
Now load the schemas (in order) by this command line (substitute the name of each LDIF file in turn):
ldapadd -x -D cn=config -y /etc/openldap/root.secret -H ldapi:/// -f core.ldif
If in the next step you get the error message: Invalid syntax (21)
additional info: olcSuffix: value #0 invalid per syntax
, it's telling
you that your suffix (or other attribute value) has invalid syntax. At this
stage of setup a very likely
reason the syntax is invalid is that the schema containing that syntax has
not been loaded, which should have happened in the previous step.
In slapd.conf.seed given above I included the TLS parameters so that TLS would be available from the beginning, for security when sending passwords. However, it is also possible to set up TLS by dynamic configuration. Here is the procedure that I formerly followed, for historical interest, together with a discussion of the TLS parameters themselves.
OpenSuSE provides the openssl package for this; certificates are stored in subdirectories of /etc/ssl. The required parameters are:
olcTLSCertificateKeyFile: /etc/ssl/private/host.key -- The host's secret key cannot be encrypted (since LDAP can't present the password), so it must have restrictive UNIX file permissions, and LDAP has to be able to read it. I handle this by creating a group called hostcert, of which ldap is made a member, and give the key file mode 640 (group readable) owned by root:hostcert, and similarly /etc/ssl/private itself must have mode 750 owned by root:hostcert.
olcTLSCertificateFile: /etc/ssl/hostcerts/host.crt -- The effort required to factorize the public key is outrageously great, so this file may be mode 644 (readable by everyone). The hostcerts subdirectory is my creation, to reduce clutter in /etc/ssl/private.
olcTLSCACertificatePath: /etc/ssl/certs -- This directory contains hash links to Certificate Authority certificates; if the client were to present a certificate signed by other than one of these CA's, it would not be accepted for admission. Actually I have not configured client X.509 authentication, but I set this parameter for completeness. To create a hash link, follow these steps (I'm assuming the CA certs are in the same directory, which is not always the case):
openssl x509 -in new-ca-cert.crt -noout -hash #Prints hex, e.g. db587806
ln -s new-ca-cert.crt db587806.0 #Increment the extension for collisions
Here is the command to configure TLS:
ldapmodify -x -D cn=config -y /etc/openldap/root.secret -H ldapi:/// <<EOF dn: cn=config changetype: modify add: olcTLSCertificateKeyFile olcTLSCertificateKeyFile: /etc/ssl/private/host.key - add: olcTLSCertificateFile olcTLSCertificateFile: /etc/ssl/hostcerts/host.crt - add: olcTLSCACertificatePath olcTLSCACertificatePath: /etc/ssl/certs - EOF
And a command to test the configuration; it uses the Internet port (ldap:// vs. ldapi:// URI) and -ZZ makes it die if TLS cannot be set up.
ldapsearch -x -D cn=config -y /etc/openldap/root.secret -H ldap://walnut.math.ucla.edu/ -ZZ -b cn=config -s base -LLL olcTLSCertificateKeyFile
The next job is to configure the level of security required for various tasks. See the man page for slapd.conf for details about security strength factors (ssf), but basically, the ssf is approximately the number of bits required for a brute force attack. That is, if the ssf were 4 than 24 = 16 attack attempts would be needed to cover all possibilities and be sure of cracking the security. 0 means no security is required, 1 means integrity checking only, and 128 is the minimum strength for modern ciphers (paranoid sysops are moving to 256 bit strength).
The parameters set here globally are inherited by all the databases. Update_ssf pertains to changing attributes, versus just reading them. In a simple bind a password is sent over the wire, and so the simple_bind parameter requires encryption. olcLocalSSF specifies the ssf which is assumed for LDAPI, i.e. access over UNIX sockets; it takes a root exploit to steal information out of the memory buffers attached to a UNIX socket, and so there is no need to set up TLS.
Here is the command and LDIF to set the ssf:
ldapmodify -x -D cn=config -y /etc/openldap/root.secret -H ldapi:/// >>EOF dn: cn=config changetype: modify replace: olcLocalSSF olcLocalSSF: 128 - replace: olcSecurity olcSecurity: update_ssf=128 simple_bind=128 - EOF
The first (and usually only) payload subtree is held in a bdb or hdb database. The documentation (Administrator's Guide v2.4 section 11.1) says they are very similar, and it is not too clear why you should pick one or the other. The provided sample slapd.conf file uses bdb. However, hdb can do subtree renames, and for maximum flexibility in recovering from bad design choices, that's the one I picked. Note the warning that idlcachesize (index cache) should be 3 times cachesize for hdb.
ldapadd -x -D cn=config -y /etc/openldap/root.secret -H ldapi:/// <<EOF dn: olcDatabase={1}hdb,cn=config olcDatabase: {1}hdb objectClass: olcDatabaseConfig objectClass: olcHdbConfig olcDbDirectory: /var/lib/ldap/data.d olcDbCheckpoint: 1024 5 olcDbCacheSize: 10000 olcDbIDLcacheSize: 30000 olcSuffix: dc=math,dc=ucla,dc=edu olcRootDN: uid=ldaproot,dc=math,dc=ucla,dc=edu olcRootPW:: {CLEARTEXT}WouldntYouLikeToKnow olcAccess: {0}to attrs=userPassword by ssf=128 self write by * auth olcAccess: {1}to attrs=shadowLastChange by ssf=128 self write by * read olcAccess: {2}to attrs=userPKCS12 by ssf=128 self read by * none olcAccess: {3}to * by * read olcDbIndex: objectClass eq olcDbIndex: entryUUID eq olcDbIndex: entryCSN eq olcDbIndex: cn eq,sub olcDbIndex: uid eq,sub olcDbIndex: uidNumber eq olcDbIndex: gidNumber eq olcDbIndex: oncRpcNumber eq olcDbIndex: ipServicePort eq olcDbIndex: ipServiceProtocol eq olcDbIndex: ipProtocolNumber eq olcDbIndex: ipHostNumber eq olcDbIndex: hostGroup eq olcDbIndex: member eq olcDbIndex: memberUid eq olcDbIndex: mail eq olcDbIndex: displayName eq,sub olcDbIndex: sn eq,sub olcDbIndex: givenName eq,sub EOF
Some notes about the database declaration:
olcDatabase: The set of databases is ordered using the {$N} prefix, and implicitly defines (?) the type of database by its name, here hdb. There can be more than one hdb database, distinguished by the ordering number, but each one needs its own directory.
objectClass: This may be what really defines what type of database is to be used. I don't know, and don't want to find out, what happens if the name and the objectClass disagree.
olcDbDirectory: Each bdb or hdb database must have a separate directory. SuSE's defalt configuration assumes you will have only one database which will be in /var/lib/ldap. If there are several, give each one its own subdirectory, as was done with slapd.d. I have done so by setting a value of /var/lib/ldap/data.d.
olcDbCheckpoint: Incoming written data is held in a transaction journal (on disc), and is committed to the actual tables according to this attribute. The parameters are the number of kbytes written and the time in minutes since the (first) write operation. When either of these is exceeded the commit occurs.
olcDbCacheSize: The number of recently used entries to keep in memory.
olcDbIDLcacheSize: The number of recently used index matches to keep in memory. For hdb it should be 3 times olcDbCacheSize.
olcSuffix: All of the database's content (including the olcRootDN) must have a Distinguished Name ending with this suffix; slapd cues on the suffix to know which database to find the content in.
olcRootDN and olcRootPW:: The complete Distinguished Name of the root user, and its password hash. In theory you could have a different root user or password for each database. The root user can read and write anything regardless of ACLs. Be sure to replace the sample password with your own. Passwords are usually hashed and base64 encoded, signalled by the double colon, because they usually contain shell-active characters.
olcAccess: An ordered list of access control lists (ACLs). See the Admin Guide section 8.3 for the syntax of an ACL. The ones shown are the provided default for the POSIX schema.
olcDbIndex: Specifies maintained indices on attribute values.
For example, responding to a search of '(uidNumber=500)', slapd could
immediately get a list of all entries (typically just one)
whose numeric UID had this
value, rather than reading every entry in the database to find it.
The parameters are the attribute to index and the comparison style;
equality is the most useful, and the sub
indices are there
because they were specified in the default slapd.conf. I have
provided indices for the key and value in all the normal UNIX directory
files. objectClass, entryUUID and entryCSN are indexed to improve
performance when replicating the database. The six at the end of
the list were inherited from the default slapd.conf, and may be
useful for PIM products.
You need to create a monitor database, if only to shut up its incessant whining that you're missing out on the latest cool feature. Here is what you need to feed to ldapadd:
ldapadd -x -D cn=config -y /etc/openldap/root.secret -H ldapi:/// <<EOF dn: olcDatabase={2}monitor,cn=config objectClass: olcDatabaseConfig olcDatabase: {2}monitor olcAccess: to dn.subtree=cn=Monitor by ssf=128 dn.exact=uid=ldaproot,dc=math,dc=ucla,dc=edu write by users read by * none olcRootDN: cn=ldaproot,cn=Monitor olcRootPW:: {CLEARTEXT}WouldntYouLikeToKnow EOF
Now it's time to engage replication. But which replication style is the best? Looking in the Admin Guide section 18.3, here are the available styles. For political correctness the guide refers to producer and consumer servers, rather than masters and slaves. Actually the same server is often both a producer and a consumer, so the change in terminology is useful.
Syncrepl -- Normal master-slaves replication. This is simple operationally. But the master needs a special configuration, and there is a server ID number which has to be unique on each server. If the servers are geographically separated, e.g. Palo Alto, Hong Kong and Amsterdam, and the network connection between them tends to drop out, sites cut off from the master cannot write on the database; e.g. people can't change their passwords.
In my home net, network segmentation is a feature
and
has to be accomodated, so the master-slave style is not the best.
Delta Syncrepl -- Almost the same as Syncrepl, but only attributes changed on the master are propagated to and changed on the slaves, whereas normally the whole entry is propagated, wastefully including the unchanged attributes. It only works on the master-slave configuration. It is most useful when many entries have large attributes such as a photo.
If I am not going to use the master-slave style, delta-syncrepl is also not going to happen.
N-Way Multi Master -- The big attractions of this style are that the configuration can be the same for all servers, and all servers can accept database writes. But if different values are written to the same attribute of the same entry, on different servers during a network outage, separated clients may rely on information which should be the same but is different. (The difference will eventually be reconciled when the network comes back.)
It looks like this is the style I'm going to pick, even though the configuration is a little more complicated.
Mirror Mode -- This is similar to 2-way multi-master, but the configuration is simpler and the documentation claims that it better resists inconsistencies during a network outage. In reality, N-way multi-master extends mirror mode to more servers.
I have four servers, so mirror mode is out.
Proxy Replication -- If you have read access to the master but are not allowed to change its configuration, you can set up a proxy server that pulls in changes from the master and pushes them out to the slaves. You can do similarly but a bit more efficiently if you can tweak the master's configuration.
It's not clear what the advantage of this mode is, versus the normal master-slave style.
To set up N-way multi-master replication, first set up each server as a provider (master) of its payload database and a consumer of everyone else's. Here are some notes on the attributes:
olcServerID: You list a unique ID number for each server. The URL after each one must exactly match a URL that is served, so the server can identify which is its proper number.
olcOverlay: All of the replication schemes require a syncrepl provider on the master(s) so changes can be sent out.
olcSyncRepl sub-attributes:
rid: This code has to be unique within the consumer. The example copies the serverID.
provider: This is the URL at which to communicate with the other master, copied from the serverID.
starttls: Make this critical because passwords are being sent on the wire.
binddn: This user must be able to read every entry in the database. We're using the superuser of the database, i.e. with the database's suffix appended (suffix is required). At the start of this document I discuss why I take the lazy way and use the superuser, rather than having a separate replication user.
bindmethod: Simple binding checks the password in the LDAP database: the olcRootPW parameter if you bind as olcRootDN. SASL binding is not as low tech, and in particular, GSSAPI (Kerberos) is attractive. But in my case the Kerberos database is going to be stored in LDAP and so there is a chicken and egg issue at runtime. Also, simple binding uses a credential in a file and/or in plain text in the olcSyncRepl attribute, whereas Kerberos decrypts the ticket using a keytab file, which is not that much stronger. So I have not been aggressive making GSSAPI work for replication.
credential: Unfortunately the ldaproot password must appear here in plain text (no algo identifier). The paranoid issues surrounding this parameter, and the choice to use ldaproot versus a separate replication user, were discussed earlier. Beware, I got burned by a shell-active character ($) in the credential, which caused ldapmodify to send in the wrong credential.
searchbase: Normally this would be the suffix of the database, but you could specify the root of a subtree, e.g. ou=People,dc=math,dc=ucla,dc=edu, to only replicate that subtree.
type: refreshAndPersist is for push
replication: the
search never finishes and continues to return modified entries.
It can also be set to refreshOnly: the search returns all entries
once, then finishes; it is repeated according to the interval
attribute.
retry: This is a list of alternating times (seconds) and counts. If there is an error, the search is retried after so many seconds, N times. '+' for the last count means infinity.
ldapmodify -x -D cn=config -y /etc/openldap/root.secret -H ldapi:/// <<EOF dn: cn=config changetype: modify add: olcServerID olcServerID: 1 ldap://sunset.math.ucla.edu olcServerID: 2 ldap://tupelo.math.ucla.edu olcServerID: 3 ldap://walnut.math.ucla.edu dn: olcOverlay=syncprov,olcDatabase={1}hdb,cn=config changetype: add objectClass: olcOverlayConfig objectClass: olcSyncProvConfig olcOverlay: syncprov dn: olcDatabase={1}hdb,cn=config changetype: modify add: olcSyncRepl olcSyncRepl: rid=001 provider=ldap://sunset.math.ucla.edu starttls=critical binddn="uid=ldaproot,dc=math,dc=ucla,dc=edu" bindmethod=simple credentials="WouldntYouLikeToKnow" searchbase="dc=math,dc=ucla,dc=edu" type=refreshAndPersist retry="60 1 300 12 7200 +" timeout=1 olcSyncRepl: rid=002 provider=ldap://tupelo.math.ucla.edu starttls=critical binddn="uid=ldaproot,dc=math,dc=ucla,dc=edu" bindmethod=simple credentials="WouldntYouLikeToKnow" searchbase="dc=math,dc=ucla,dc=edu" type=refreshAndPersist retry="60 1 300 12 7200 +" timeout=1 olcSyncRepl: rid=003 provider=ldap://walnut.math.ucla.edu starttls=critical binddn="uid=ldaproot,dc=math,dc=ucla,dc=edu" bindmethod=simple credentials="WouldntYouLikeToKnow" searchbase="dc=math,dc=ucla,dc=edu" type=refreshAndPersist retry="60 1 300 12 7200 +" timeout=1 - add: olcMirrorMode olcMirrorMode: TRUE - EOF
Do this on each server. The first server will fail to establish its connection to the others that haven't been configured yet, so you will have to either wait for the connection to be retried, or restart the server, which will then kill the connections to it, which will be restored after a (shorter) timeout. Be patient and watch syslog to identify when everyone has connected.
Now load users and other system tables into the payload database on one server. It doesn't matter which one. Suggestion: at first do just one entry and test it. Test if you can retrieve the content from the other servers. Here is a sample command line for testing, using my user entry. Substitute the Distiguished Name of the entry you added and pick an attribute that it actually has, instead of gecos, if it isn't a user.
ldapsearch -x -H ldap://walnut.math.ucla.edu -b uid=jimc,ou=People,dc=math,dc=ucla,dc=edu -LLL gecos
Turning on replication for the configuration database is very similar to doing it for the payload, and the Admin Guide actually suggests that you do this first, before setting up your payload database, which you would allow to be replicated. However I had no end of trouble, because altering or removing schema and database entries on a running server is really bad mojo. For example, my payload database would randomly disappear on some but not all servers. Maybe it would have worked in a pure master-slave configuration, but I decided to cut my losses, wipe the whole database, and start from scratch without replicating the configuration.
The configuration is now finished, and you can populate your LDAP server group with user and system tables. Putting this into practice is not exactly part of this document, but I have written:
Now we need to find clients that use the directory service, and to tell them to use LDAP.
Most clients that use directory services go through library routines such as getpwnam and its numerous friends, and these all look at /etc/nsswitch.conf to know where to get the information.
In a NIS installation, most of the directory data is obtained preferentially from NIS, with a fallback to local files. The exceptions are passwd, shadow and group, where local system users need to override NIS if present. I have followed the same approach in my nsswitch.conf . This file omits the comments at the start of the file.
passwd: files ldap shadow: files ldap group: files ldap hosts: files dns networks: files dns services: files ldap protocols: files ldap rpc: ldap files ethers: ldap files netmasks: files netgroup: ldap files publickey: files bootparams: files automount: files aliases: ldap files
According to the documentation LDAP is quite efficient, and on Solaris
NIS is used for all tables except passwd, shadow and group, with a fallback
to files. It is very tempting to put ldap
first on as many tables
as possible. But experience shows that this is not feasible on Linux
because of noisy error messages produced at boot time by daemons which start
before the network is up and LDAP is available. The settings shown above
(probably) have ldap first on the maximum number of tables.
Our home directories are exported via NFS. Details of mounting are handled
mostly by generic files that are the same for all clients, but there is one
exception, the auto_home map. Whereas SuSE, Ubuntu, etc. normally put
users' home directories directly under a locally mounted /home, we have
extended this usage through automounting. Formerly /etc/auto.master had
a row saying /home yp:auto.home
. This is replaced by:
/home ldap:ou=Auto_home,dc=math,dc=ucla,dc=edu
rfc822bis.schema defines an objectClass called automount
. Each user
has an entry with (at Mathnet) a Distinguished Name of
automountKey=${user},ou=Auto_home,dc=math,dc=ucla,dc=edu and with these
attributes:
automountKey -- The user's loginID.
automountInformation -- NFS mount string for the user's home directory, e.g. julia:/h1/maint/jimc (julia is the 1-component name of the host where the home directory is located).
Apparently it's customary to put these objects as leaves under a container
of objectClass automountMap, whose main purpose is to have an attribute of
automountMapName. However, Mathnet has not done that; all tables are leaves of
Organizational Unit objects, as is normal for rfc822bis. However, autofs
complains automount[30569]: lookup_read_map: lookup(ldap): Unable to
download entire LDAP map for: /home
. Perhaps we need to put some effort
into using the automountMap container.
At least on a SuSE system, autofs (or actually, its plugin /usr/lib64/autofs/lookup_ldap.so) consults /etc/sysconfig/autofs and looks for these settings, as used at Mathnet:
DEFAULT_MAP_OBJECT_CLASS="organizationalUnit" DEFAULT_MAP_ATTRIBUTE="ou" DEFAULT_ENTRY_OBJECT_CLASS="automount" DEFAULT_ENTRY_ATTRIBUTE="automountKey" DEFAULT_VALUE_ATTRIBUTE="automountInformation"
As mentioned, normally for this schema DEFAULT_MAP_OBJECT_CLASS="automountMap".
In /etc/postfix/main.cf the alias_maps parameter starts out as
hash:/etc/aliases, nis:mail.aliases
. The latter term should be
changed to
alias_maps = hash:/etc/aliases, ldap:/etc/postfix/ldap-aliases.cf
The SuSE package provides a sample file called ldap_aliases.cf which is for a schema that we don't use. Apparently there are quite a number of alias schemas, each incompatible with the others. Ours uses an objectClass called nisMailAlias from misc.schema. Each user or other recipient has an entry of this class with a Distinguished Name of cn=${user},ou=Aliases,dc=${realm},dc=ucla,dc=edu where ${realm} is math or pic. The entry has two attributes:
cn -- the loginID of the user (or other mail recipient) without @domain. Primary key forming the Distinguished Name.
rfc822MailMember -- E-mail address to which mail for that user
should be sent, normally with the domain
(1-component hostname).
The ldap-aliases.cf file which works for us looks like this:
# Testing LDAP alias map -- jimc, 2012-02-19 # See http://www.postfix.org/LDAP_README.html and man 5 ldap_table # A lot of items are realm-specific (pic vs math). # In main.cf set: # alias_maps = hash:/etc/aliases, ldap:/etc/postfix/ldap-aliases.cf # Why couldn't they have utilized /etc/ldap.conf? Needs sorthosts treatment. # Port can be included; sorthosts needs to take this into account. server_host = ldap://sunset.math.ucla.edu ldap://sumac.math.ucla.edu ldap://tupelo.math.ucla.edu ldap://walnut.math.ucla.edu # Where to look for the aliases. %u = user without @domain. search_base = cn=%u,ou=Aliases,dc=math,dc=ucla,dc=edu # The filter must be specified to override the lameass default. query_filter = (cn=%u) # The attribute(s) containing the alias (lookup value) result_attribute = rfc822MailMember # scope default is sub, but is irrelevant since these are leaf nodes. # The alias map can be accessed anonymously and without TLS. bind = no
It is possible for Kerberos to store its database in LDAP, utilizing LDAP's replication capability, which is an area Kerberos is weak in. See this separate document about putting the Kerberos Database in LDAP.
You first need to install the pam_ldap package. It gives you the module /lib64/security/pam_ldap.so, a man page for pam_ldap, and a bunch of sample PAM scripts in /usr/share/doc/packages/pam_ldap/pam.d . Per README.SuSE, you just need to edit /etc/security/pam_unix2.conf and add the "use_ldap" option for account, auth and password management. However, the currently provided pam_unix2.so does not read that file; it is sensitive to /etc/nsswitch.conf /etc/default/passwd and /etc/login.defs . It will be necessary to specify use_ldap on the command line of pam_unix2.so, or else explicitly call pam_ldap. The sample PAM scripts show how to do the latter strategy.
Add these parameters to /etc/ldap.conf. (These are already configured in the default /etc/ldap.conf .)
pam_login_attribute uid (is the default, per RFC 2307) -- This is the attribute used to make the first component of the user's Distinguished Name.
pam_filter -- To be appended to the search filter; normally omitted.
pam_password exop -- Pick the algorithm for changing the password.
exop
is for OpenLDAP using the extended operation defined in
RFC 3062. Several variants are available including ad
for
Microsoft Active Directory.
I tried adding use_ldap
to the pam_unix2.so command line, but this
was not successful. First, pam_ldap.so botches the start_tls negotiation;
slapd thinks that it has set up TLS, but the client rejects the connection
for no obvious reason. The cure for this was to specify server URLs of
ldaps://host.fqdn (or LDAPI if the server is on the local host). Then
remove explicit specification of the SSL parameter (none of off, on or
start_tls). Then the client
auto-switches to no TLS when using LDAPI, and it does not botch a LDAPS
TLS connection.
The next problem is that when pam_unix2.so uses ldap
it does not
pass through the use_first_pass parameter, and so pam_ldap unconditionally
asks again for the LDAP Password
. This is really user unfriendly,
and so we are going to have to specify pam_ldap.so in the PAM scripts.
Here is how I have them set up:
For authentication there are three mechanisms: Kerberos, LDAP and files
(pam_unix2.so). If one of them accepts the user we want to skip the others.
The sample PAM scripts show one effective way to accomplish this: make each of
them sufficient
(and don't have any subsequent modules).
We want Kerberos first because it can save the credential (ticket-granting
ticket) and store it in the session phase.
We want to do pam_ldap.so next, before pam_unix2.so, because pam_ldap checks
authentication by
doing a simple bind with the target user's password. Pam_unix2, in the likely
case that it doesn't find the user in the local /etc/shadow, retrieves
the hashed password from LDAP by binding with the root secret, and checks
the password locally. This seems to me to be more heavyweight, so I set it
up so pam_unix2 will be skipped most of the time.
For authorization, pam_krb5 checks ~/.k5login and password expiration
(if configured, which we don't). Pam_ldap checks some authorization fields
involving groups, which we don't use. Pam_unix2 checks password expiration.
In reality Mathnet handles password aging and expiration in its own way, so
all of these are irrelevant except .k5login, but in the authorization phase
all the modules should be executed. Thus they are made required
.
However, if the user is not in Kerberos or LDAP it will cause authorization to fail, which is not what we want. Thus I have added the ignore_unknown_principals parameter to pam_krb5 and both ignore_unknown_user and ignore_authinfo_unavail to pam_ldap. I have also added use_first_pass so pam_ldap so it won't ask for a password again. This is the default for pam_krb5 and I had trouble with it in the past, so I didn't put it there. It is also the default for pam_unix2, but it's OK to specify it explicitly as I have done.
For password changing, the sample files have all mechanisms required
.
I think this is a mistake: if any mechanism fails, we want to do the others
anyway, because it's common for system users to be in files
but not LDAP and Kerberos, or ordinary users to be in LDAP and Kerberos but
not files. Cases in LDAP but not Kerberos will be common during initial
deployment. Thus I've made all the mechanism calls optional
.
They all also need use_first_pass so they will rely on the old password
collected by pam_pwcheck.so. Try_first_pass is tempting, so a user whose
password gets out of sync in the various mechanisms has a chance to fix
the situation, but then if the user does not exist for a particular mechanism
the module will try to improve
the situation by asking for a possibly
out-of-sync different password, which is going to be very annoying. Therefore
I have specified use_first_pass for all mechanisms. It doesn't help to
specify ignore_unknown_principal, at least for pam_krb5, but specifying the
ignore_unknown parameters suppresses error messages in syslog for such users.
For session setup, all the mechanism calls are optional
,
for the same reason I did this in the password phase.
In reality pam_ldap has nothing to do in this phase
and is omitted in the sample PAM scripts.
LDAP directory browser software:
JXplorer -- an open source ldap browser, the world's finest
LDAP browser
. Latest version 3.2.1 (2010-05-22). Java.
Project home page.
Apache Directory Studio, LDAP Browser Plugin -- Powerful searches. Can export subtrees in LDIF, DSMLv2, CSV, XLS (!). Project home page. Based (?) on Eclipse. Includes schema browser. Written in Java.
ldapbrowser -- I spotted this link, don't know anything about it. Project home page.
Link to test script for setting up multi master
Look in admin guide sect. 12.8, Reverse Group Membership Maintenance, for a way to invert the group map to put group memberships into the posixAccount objects. See also man slapo-memberof .
Web resource, a book about LDAP: http://www.zytrax.com/books/ldap/