OpenLDAP installation and configuration: Difference between revisions
m (using an external editor) |
mNo edit summary |
||
(43 intermediate revisions by the same user not shown) | |||
Line 2: | Line 2: | ||
== Introduction == | == Introduction == | ||
OpenLDAP is the most popular free [[LDAP]] server. | OpenLDAP is the most popular free [[LDAP]] server. Documentation is not obvious for beginners, i.e. it takes some time learn how to install and configure a production server. | ||
OpenLDAP 2.x software implements version 3 of LDAP (RFC 4510) | |||
A '''lot''' of this information is much better explained in the excellent [http://www.zytrax.com/books/ldap/ LDAP for Rocket Scientists] guide. For the moment, these are just installation, design and configuration notes [[User:Daniel K. Schneider|Daniel K. Schneider]] made while redesigning a simple directory to include information about people and things in our little organization. Many (short) explanations are just rephrasing or even cut/paste from their "attribution-noncommercial 2.2" licensed text. | |||
== Configuration notes for solaris 10 == | == Configuration notes for solaris 10 == | ||
Line 9: | Line 13: | ||
/opt/sfw | /opt/sfw | ||
/opt/sfw/sbin - binaries | /opt/sfw/sbin - links to binaries | ||
/opt/sfw/libexec - binaries | |||
/opt/sfw/etc/openldap - configuration files | /opt/sfw/etc/openldap - configuration files | ||
/opt/sfw/var/openldap-data - default data | /opt/sfw/var/openldap-data - default data | ||
/opt/sfw/var/run - PID of the server | |||
;Binaires in /opt/sfw/sbin: | |||
slapadd -> ../libexec/slapd | |||
slapcat -> ../libexec/slapd | |||
slapdn -> ../libexec/slapd | |||
slapindex -> ../libexec/slapd | |||
slappasswd -> ../libexec/slapd | |||
slaptest -> ../libexec/slapd | |||
slapcat -> ../libexec/slapd | |||
slapdn -> ../libexec/slapd | |||
slapindex -> ../libexec/slapd | |||
slappasswd -> ../libexec/slapd | |||
slaptest -> ../libexec/slapd | |||
=== The configuration file === | |||
Location: | |||
/opt/sfw/etc/openldap/sladpd.conf | |||
You will have to define | |||
* What schemas to load in | |||
* Where datafiles and pidfile etc. go | |||
* What users are allowed to do | |||
Here is a fictional example (comments taken away from the original): | |||
<pre> | |||
include /opt/sfw/etc/openldap/schema/core.schema | |||
include /opt/sfw/etc/openldap/schema/cosine.schema | |||
include /opt/sfw/etc/openldap/schema/inetorgperson.schema | |||
include /opt/sfw/etc/openldap/schema/nis.schema | |||
# Add your own | |||
include /opt/sfw/etc/openldap/schema/tecfa.schema | |||
pidfile /opt/sfw/var/run/slapd.pid | |||
argsfile /opt/sfw/var/run/slapd.args | |||
security ssf=1 update_ssf=112 simple_bind=64 | |||
# Access contol (this is too minimalistic) | |||
access to attr=userpassword | |||
by self write | |||
by anonymous auth | |||
access to * | |||
by self write | |||
by users read | |||
database bdb | |||
# Suffix and root dn, adjust to your own organization | |||
suffix "dc=tecfa, dc=unige, dc=ch" | |||
# Note: The root uid does not need to be in the LDAP database | |||
rootdn "uid=xxxx, dc=tecfa, dc=unige, dc=ch" | |||
rootpw secret | |||
directory /opt/sfw/var/openldap-data | |||
index objectClass eq | |||
</pre> | |||
You may want to put the data and schema files in some other place than the default, since you may by mistake kill them after an upgrade of the system. e.g. I used /var/openldap instead of /open/sfw/var/ | |||
=== The Root DSE, suffix, naming context etc === | |||
* The LDAP protocol assumes there are one or more servers that jointly provide access to a Directory Information Tree (DIT). | |||
* The '''Root DSE''' is the topmost (and "invisible" entry) in the LDAP hierarchy. This DSA-specific entry publishes information about the LDAP server including which LDAP versions it supports, any supported SASL mechanisms, supported controls as well as the DN for its subschemaSubentry. In addition to server information, operational attributes may be exposed that allow for extended administration functionality. Client software will usually access this kind of information. The root DSE (DSA-specific Entry) data can be retrieved from an LDAPv3 server by doing a base-level search with a null BaseDN and with filter ObjectClass=*. | |||
* The '''Directory Information Tree''' (DIT), also called '''naming-context''' refers to the hierarchy of objects that make up the directory structure. More than one DIT may be supported by an LDAP server. A DIT or Naming Context defines a namespace within which information can be searched. | |||
* The ''root'', ''suffix'' or ''base'' refers to the topmost entry. In practical terms it's the name of the DIT and is part of every distinguised name. | |||
* The '''rootdn''' is the id of the superuser. You have to define it and its password in the configuation file (see below). | |||
* ''Directy System Agent" (DSA) means (basically) "LDAP server". It's X500 slang. | |||
In practical terms, you have at some point to decide how to organize the structure of the LDAP. In a first version I had all objects under the root dn and the DIT had the simple suffix: "o=tecfa.unige.ch". | |||
In a later version I decided to adopt a more common RFC 227 practise and to use the name of the principal web server as suffic. To do so, you must use "dc" (domain component) syntax: | |||
E.g. a domain names like | |||
tecfa.unige.ch | |||
becomes | |||
dc=tecfa,dc=unige,dc=ch | |||
This has to be defined in the ''slapd.conf'' file as explained above | |||
suffix "dc=tecfa, dc=unige, dc=ch" | |||
And the super power user (name "xxxx" here), identified by "rootdn" becomes something like: | |||
rootdn "uid=xxxx, dc=tecfa, dc=unige, dc=ch" | |||
But bascially speaking you can do whater you like. Actually there is a second "standard". ISO suggested something like | |||
ou=tecfa,o=unige,c=ch | |||
=== Importing / exporting an LDIF file === | |||
To import an LDIF file: | |||
/opt/sfw/sbin/slapadd -v -l your-ldif-file.ldif | |||
To export an LDIF file: | |||
/opt/sfw/sbin/slapcat > file_name.ldif | |||
Warning: When I exported, then '''modified the structure''' (e.g. put people inside a ou), then imported ldifs again I noticed that I had to kill all (well some at least) attributes added by the server ! | |||
E.g. | |||
entryUUID: .... | |||
entryCSN: .... | |||
If I didn't kill these, I could find anything anymore. Maybe an import/export option I didn't get ... | |||
To add or modify entries | |||
ldapadd -D "cn=rootid,dc=tecfa,dc=unige,dc=ch -f addgroups.ldif -w secret | |||
Change the rootid, the *.ldif file name and the secret password ... | |||
Also read the manual page about this. Adding entries that are really new is easy, but modifying old ones requires special ldif commands. | |||
The '''difference''' (in a nutshell) between slapadd and ldapadd is that you use slapadd to create a new database (each time: stopping the server, kill all the database files, starting, importing) until you are happy. Once you like your LDAP, then you should use ldapadd to make changes or use some client software. | |||
If you just make small changes, you can just use a GUI client to so. LDIF is never needed, it's just more practical for addition / modifcation of many entries. Some clients also have an ldif import button or even a cut/paste area. E.g. the rather nice [http://phpldapadmin.sourceforge.net/ phpLdapAdmin] web client does. | |||
=== Testing with a client === | |||
You can test your LDAP through an LDPA client like [http://directory.apache.org/studio/ Apache Directory Studio]. To connect to your LDAP server, make sure that the port is open both on your client machine and the server machine. By default LDAP uses port 389. | |||
We suggest to install "Apache Directory Studio". To configure a connection to an LDAP server: Menu->LDAP->New connection | |||
In the Authentication tab enter: | |||
Bind DN or user: <the root dn you defined above> | |||
Else, you may have the command line [http://docs.sun.com/source/816-6400-10/lsearch.html ldapsearch] installed. More difficult, but a better bet for debugging. | |||
Syntax for connecting as LDAP administrator: | |||
ldapsearch -b <starting point> -D <user dn for connecting> -w - filter | |||
Example: The following will display all attributes of the object "uid=joemiller" in a database that has root dn (suffix) of "c=tecfa,c=unige,c=ch" and people objects inside "ou=people". It will display all attributes for joe. | |||
ldapsearch -b "ou=people,dc=tecfa,dc=unige,dc=ch" -D "uid=root,dc=tecfa,dc=unige,dc=ch" -w - "uid=joemiller" | |||
Example: The following will search for "Daniel" in givenname(s) and display what it can to an anonymous bind (user). Notice that the people in the example below are just below the DIT root (good enough for a simple addressbook). | |||
ldapsearch -b "o=tecfa.unige.ch" -v "givenname=Daniel" | |||
* "''b <starting point>''" refers to the top of the tree you want to search. It must be a distinguished name, e.g. "ou=people,dc=example,dc=com". Starting points can be very different from server to server. | |||
* "''-w -''" will have it prompt for a password. | |||
* "-h host" allows to specify a host, by default it is localhost | |||
* "''-v'' will make verbose output (diagnostics). | |||
=== The startup script === | |||
To start/stop automatically the server you can write a script like this, put it in /etc/init.d and then make links from /etc/rc3.d, /etc/rc0.d etc. | |||
<pre> | |||
#!/bin/sh | |||
STARTCMD="/opt/sfw/libexec/slapd" | |||
LISTENPORTS="ldap:/// ldaps:///" | |||
STOPCMD="kill -INT `cat /var/openldap/run/slapd.pid`" | |||
# These are some string we reuse to give feedback. | |||
DESC="OpenLDAP standalone Deamon (slapd)" | |||
ERRORMSG="!!!! ERROR:" | |||
# This is used to see if slapd is running. Contains null if it doesn't | |||
# or a process description if it does. | |||
ISRUNNING=`cat /var/openldap/run/slapd.pid` | |||
# Now we check for the argument given on command line | |||
# and act unpon its value | |||
case "$1" in | |||
'start') | |||
# We test if the server is not already running | |||
if [ -z "$ISRUNNING" ] ; | |||
then | |||
# We test if the server is effectively started when the command is issued. | |||
if $STARTCMD -h "$LISTENPORTS" ; | |||
then | |||
echo "$DESC started" ; | |||
else | |||
echo "$ERRORMSG $DESC could not be started" | |||
exit ; | |||
fi | |||
else | |||
echo "$ERRORMSG $DESC is already running" ; | |||
fi | |||
;; | |||
'stop') | |||
# We test if the server is already running | |||
if [ ! -z "$ISRUNNING" ] ; | |||
then | |||
# We test if the server is effectively stopped when the command is issued. | |||
if $STOPCMD ; | |||
then | |||
echo "$DESC stopped" ; | |||
else | |||
echo "$ERROR $DESC could not be stopped" ; | |||
fi | |||
else | |||
echo "$ERRORMSG $DESC is not running" | |||
exit ; | |||
fi | |||
;; | |||
'restart') | |||
# We test if the server is already running | |||
if [ ! -z "$ISRUNNING" ] ; | |||
then | |||
# We test if the server is effectively stopped when the command is issued. | |||
if $STOPCMD ; | |||
then | |||
# We test if the server is effectively started when the command is issued. | |||
if $STARTCMD -h "$LISTENPORTS" ; | |||
then | |||
echo "$DESC restarted" ; | |||
else | |||
echo "$ERRORMSG $DESC stopped but not restarted" | |||
exit ; | |||
fi | |||
else | |||
echo "$ERRORMSG $DESC could not be stopped (and hence not restarted)" | |||
exit ; | |||
fi | |||
else | |||
echo "$DESC is not running: Starting" ; | |||
if $STARTCMD -h "$LISTENPORTS" ; | |||
then | |||
echo "$DESC started" ; | |||
else | |||
echo "$ERRORMSG $DESC could not be started" | |||
exit ; | |||
fi | |||
fi | |||
;; | |||
'debug') | |||
# We test if the server is already running | |||
if [ ! -z "$ISRUNNING" ] ; | |||
then | |||
echo "Server was running: stopping" | |||
if $STOPCMD ; | |||
then | |||
echo "STOPPED" ; | |||
else | |||
echo "$ERRORMSG $DESC could not be stopped" | |||
exit ; | |||
fi | |||
fi | |||
# we look if a second argument is given for debug level | |||
if [ -z "$2" ] | |||
then | |||
LOGLEVEL="4095" ; | |||
else | |||
LOGLEVEL="$2" ; | |||
fi | |||
echo "attempting to start $DESC" | |||
echo "in debug mode with loglevel $LOGLEVEL" | |||
echo | |||
echo "terminal will remain open if it succeeds" | |||
echo "exit with CTRL-C" | |||
# Starting the server in debug mode | |||
if $STARTCMD -d $LOGLEVEL -h "$LISTENPORTS" ; | |||
then | |||
# this is a little tricky because the message outputs only | |||
# when Ctrl-C is issued. | |||
echo "$DESC STOPPED..." ; | |||
else | |||
echo | |||
echo | |||
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!" | |||
echo "$ERRORMSG $DESC could not be started in debug mode" | |||
echo "$ERRORMSG or has exited abnormaly" | |||
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!" | |||
echo "Check for error messages in the debug trace" | |||
echo | |||
exit ; | |||
fi | |||
;; | |||
*) | |||
# We show how to use that script if the given argument is not recognized. | |||
echo "**************************************************************************" | |||
echo "* Usage : /etc/init.d/openldap.server {start|stop|restart [debug_level]} *" | |||
echo "**************************************************************************" | |||
;; | |||
esac | |||
</pre> | |||
See also on the Internet, e.g. [http://www.linagora.org/article123.html OpenLDAP Start/stop script] from LinAgora.org. | |||
=== Installation notes for Ubuntu 4.1 === | |||
OpenLDAP is distributed through the Synaptic Package Manager. | |||
* The installer will ask the rootdn password. | |||
* It also will automatically launch the LDAP server (use Menu System->Administration->Services to stop it again) | |||
Configuration files are in: | |||
/etc/ldap/ | |||
The rest is the same. | |||
== Operational attributes == | |||
There exist so-called operational attributes like "modifyTimestamp". They are not returned unless you specifically request it. You must also have the appropriate permission to view the attribute. | |||
In OpenLDAP, you can ask for "+" to fetch all the operational attributes | |||
for an entry. That would look something like this: | |||
ldapsearch -LLL -W -D "cn=Manager,dc=example,dc=com" -b | |||
"cn=Christoph,ou=People,dc=dexample,dc=com" "(objectClass=*)" '+' | |||
Or just ask for the "modifyTimestamp" attribute by name. The "+" is | |||
similar in concept to the "*" which returns all the non-operational | |||
attributes. | |||
Bascially speaking, an LDAP server will add attributes you didn't define. Some of these are useful in client applications, e.g. time stamps or who did what... | |||
== Organization of the directory tree == | |||
Basically, you should keep the structure simple and flat for the simple reason that moving around objects is much more complication than just changing an attribute. E.g. a student may become a teaching assistant and then a lecturer. So putting people in more than one group does not make sense in our case. | |||
=== An example ldif for an extended address book === | |||
This start of an ldif file shows the following: | |||
* The base naming context is '''dc=tecfa, dc=unige, dc=ch''' | |||
The root (suffix) is defined like this: | |||
<pre> | |||
dn: dc=tecfa, dc=unige, dc=ch | |||
dc: tecfa | |||
objectClass: dcObject | |||
objectClass: organization | |||
o: TECFA | |||
</pre> | |||
All people go inside a single organizational unit: '''dn: ou=people, dc=tecfa, dc=unige, dc=ch'''. Else if you distinguish between students, teachers and so forth, you will have to move entries once persons change status (see also [http://www.zytrax.com/books/ldap/apa/structure.html "Flat is Good"]). | |||
In other words, each person has a dn like this: | |||
dn: uid=xxxx, ou=people, dc=tecfa, dc=unige, dc=ch | |||
<pre> | |||
dn: ou=people, dc=tecfa, dc=unige, dc=ch | |||
objectClass: top | |||
objectClass: organizationalUnit | |||
ou: people | |||
description: All people in the LDAP | |||
structuralObjectClass: organizationalUnit | |||
dn: uid=dksuid, ou=people, dc=tecfa, dc=unige, dc=ch | |||
objectClass: person | |||
objectClass: organizationalPerson | |||
objectClass: inetOrgPerson | |||
objectClass: tdsTecfaPerson | |||
givenName: Daniel | |||
sn: Schneider | |||
cn: Daniel Schneider | |||
uid: schneide | |||
description: Maitre d'enseignement et de recherche | |||
structuralObjectClass: tdsTecfaPerson | |||
tdsMemberCategory: TECFA bureau | |||
tdsMemberCategory: TECFA member | |||
tdsMemberCategory: TECFA teacher | |||
personalTitle: Dr. | |||
.... some entries deleted .... | |||
</pre> | |||
One might also add an attribute that points from a user to one or more "ou"s (organizationalUnitName) and define several "organizationalUnit"s in order to "tag" people into categories (but so far I didn't). For the moment we just use some custom attributes and simple strings, but that's not as "safe" as design since in an LDAP client you could enter just anything into a "tdsMemberCategory" field. Note: giving people several "ou" attributes does not mean that they are in these "ou" subtrees. It's just a pointer to an "ou". | |||
ou=teachers | |||
ou=students | |||
ou= .... | |||
Before adding more trees and objects and relations that may go with it, you have to consider that this means writing more complicated code to edit or even just to display information. One reason to adopt LDAP for "information purposes" is that one can avoid the kind of horrible PHP programming one has to do with SQL databases;). With LDAP, you basically can just use an LDAP client to edit information and then produce some simple lists .... | |||
=== Using LDAP to authenticate users in web portals === | |||
Probably it's a good idead to use the posixAccount which is in the nis.schema because one then also could use it for [[single sign-on]] login authentication (e.g. on various linux machines, samba, etc.) .... later maybe | |||
== Creating your own LDAP schema == | |||
Once you decided to adopt LDAP to manage information about users and other things in your organization, you will find out that the standardized schemas - although huge - are not enough. | |||
So, in the slapd.conf file we added our own: | |||
include /opt/sfw/etc/openldap/schema/tecfa.schema | |||
* all entries start with "tds" in order to distinguish them a bit from the huge pile of existing ones | |||
For instance, there is an attribute that allows to define a skill (e.g. "LDAP" or "knitting") | |||
<pre> | |||
# Person's compentencies | |||
attributeType ( 1.3.6.1.4.1.11389.1.3.1.14 NAME 'tdsSkill' | |||
DESC 'Description of the persons skills/compentencies' | |||
EQUALITY caseIgnoreMatch | |||
SUBSTR caseIgnoreSubstringsMatch | |||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15) | |||
</pre> | |||
See the [[LDAP]] article for some more information about schema writing. | |||
== Access control (ACL) == | |||
(not really complete) | |||
OpenLDAP 2.0 contains two methods for specifying access control. The first is static, i.e. you define the rights in configuration files. wo other advantages of this method is that it should be more efficient in most cases and that the rules, being static, cannot be changed by external means using LDAP so it should be more secure. From an operational point of view, the problem of this method is that needs a server restart at every Access contol (ACL) change. (From the [http://www.openldap.org/faq/ FAQ], Configuration / SLAPD Configuration) | |||
The second method for access control inserts access control information inside the directory itself. Unfortunately, the standard for doing this in a way that is interoperable between servers of different vendors (this did not matter in the static config case) has not been finished and exists only as an Internet Draft (i.e. no RFC has been published and the specification might not even get enough consensus for an RFC to be published ever). (From the [http://www.openldap.org/faq/ FAQ]) | |||
=== Via the old method (static) === | |||
access to <what> [ by <who> <access> [ <control> ] ]+ | |||
* Read [http://www.zytrax.com/books/ldap/ch5/step2.html 5.2 Securing the Directory] (from the "Rocket scientist" manual). | |||
* Read through the subcategories of [http://www.openldap.org/faq/data/cache/448.html More information about Access Control] (scroll down the page and don't miss any links or examples ...) | |||
Firstly you need to know about two principles: | |||
* '''The general rule is:''' write special access rules first, generic access rules last. | |||
* Never write more than a single rule about something. E.g. you can't have two rules rules that define ''acces to *''. | |||
Let's have a look at a few ACL patterns: | |||
By default, anyone and everyone can read anything but but only the rootdn can make any updates (need to verify this). This implicit rule could be made explicit like this: | |||
<pre> | |||
access to * | |||
by * read | |||
</pre> | |||
First rule you may concerns the user passwords. Users can update but not read their password and anonymous users can authenticate (else no user can log in). | |||
<pre> | |||
access to attrs=userpassword | |||
by self =xw | |||
by anonymous auth | |||
</pre> | |||
"self" refers to the user. | |||
The following rule means that the owners have full access to their entry and users can read everything. This is something you probably don't want, i.e. you might want to show some information (e.g. homepages or email addresses to a public at large (anonymous readers) | |||
<pre> | |||
access to * | |||
by anonymous none | |||
by self write | |||
by users read | |||
</pre> | |||
The following allows anonymous users to read Common Names (cn) | |||
<pre> | |||
access to cn | |||
by anonymous read | |||
</pre> | |||
=== Defining groups === | |||
One way to differentiate rights amoung the users is to create groups for them. | |||
Firstly we define an object that represents a branch in the DIT within which we then can define various groups. | |||
<pre> | |||
dn: ou=groups,dc=tecfa,dc=unige,dc=ch | |||
objectclass:organizationalunit | |||
ou: groups | |||
description: DIT tree for various groups | |||
</pre> | |||
Then we can define groups inside. Here we define a "managers" group. I will have the right to change about everything in the DIT. | |||
* It has the following ''dn'' (distinguished unique name) components: | |||
** a name (the "cn=managers") | |||
** the "ou=groups" will peut it inside the groups tree | |||
** the global suffix for the LDAP naming context. | |||
This object belongs to the class "groupOfNames" which is defined in both the core.schema and the cosine.scheme, e.g. schemas that you have by default in your LDAP. This class requires that you define at least the "cn" and that you have at least one "member" attribute. If find this a bit strange, e.g. at some point a group could be empty ... | |||
<pre> | |||
dn: cn=managers,ou=groups,dc=tecfa,dc=unige,dc=ch | |||
objectclass:groupOfNames | |||
ou: groups | |||
cn: managers | |||
description: People that can change most entries in the LDAP (i.e. some TECFA employees). | |||
member: uid=miller,ou=people,dc=tecfa,dc=unige,dc=ch | |||
</pre> | |||
Once you got such a group you then can give it rights in the slapd.conf file: | |||
<pre> | |||
access to * | |||
by self write | |||
by group.exact="cn=managers,ou=groups,dc=tecfa,dc=unige,dc=ch" write | |||
by users read | |||
by * none | |||
</pre> | |||
=== Access for php scripts and anymous users === | |||
I didn't find a satisfactory way to give read access to anonymous users | |||
For PHP scripts, I just created a dummy user, that has full read access to everything (and write access to his own stuff which doesn't matter much). So even if by chance someone manages to get hold of the script code, he/she couldn't do much dammage since password access is more strict. | |||
E.g. in the slapd.conf file I got something like this: | |||
<pre> | |||
# Anonymous users are allowed access the dn base, else a client can't do anything | |||
access to dn.base="" | |||
by * read | |||
# Users can update but not read passwords, managers can do everything, anonymous can authenticate, | |||
# read access denied to all others | |||
access to attrs=userPassword | |||
by self =xw | |||
by anonymous auth | |||
by group.exact="cn=managers,ou=groups,dc=tecfa,dc=unige,dc=ch" write | |||
by * none | |||
# for all the rest, self can change all his stuff, managers can change everything, | |||
# users can read and guest can try to search (auth is included in this permission) | |||
access to * | |||
by self write | |||
by group.exact="cn=managers,ou=groups,dc=tecfa,dc=unige,dc=ch" write | |||
by users read | |||
by anonymous search | |||
</pre> | |||
Bascially I can connect to the LDAP as anyomous user, but can't see nor list anything. Doesn't matter for now since I got this relatively safe solution for php scripts. After this LDAP only serves to manage some information for now. | |||
You can see it [http://tecfa.unige.ch/tecfa-people/tecfa-people.html here] (well we have to update entries but that's another painful issue ...) | |||
=== ACL with new method (inside the LDAP) === | |||
olcAccess: to <what> [ by <who> <accesslevel> <control> ]+ | |||
is a different way of doing it. You should read these olcAccess statements as ldif notation of an LDAP attribute. | |||
* Read [http://www.openldap.org/doc/admin24/slapdconf2.html#Access%20Control the Access Control Chapter] of the manual. | |||
* Type ''man slapd.access'' | |||
== Links == | |||
* [http://www.openldap.org/faq OpenLDAP FAQ] | |||
* [http://www.zytrax.com/books/ldap/apd/ LDAP Glossary] | |||
* [http://www.zytrax.com/books/ LDAP for Rocket Scientists] | |||
* [http://www.openldap.org/doc/ OpenLDAP Software 2.0 - 2.4 Administrator's Guides] | |||
** tip: Use the 1HTML version to search through the page... | |||
* [http://www.bind9.net/manual/openldap/2.3/slapdconf2.html Configuring slapd] (version 2.3 on Feb 2008). | |||
* [http://www.ceenet.org/workshops/lectures2001/Peter%20Gietz/tutorial.html LDAP Tutorial - Exercise with OpenLDAP v2.0.11 on Linux] by P. Gietz and N. Klasen (2001) | |||
* [http://www.geekcomix.com/cgi-bin/classnotes/wiki.pl?LDAP01 LDAP01], Sam Hart's classnotes | |||
[[Category:Installation tips]] | [[Category:Installation tips]] |
Latest revision as of 16:51, 29 January 2009
Introduction
OpenLDAP is the most popular free LDAP server. Documentation is not obvious for beginners, i.e. it takes some time learn how to install and configure a production server.
OpenLDAP 2.x software implements version 3 of LDAP (RFC 4510)
A lot of this information is much better explained in the excellent LDAP for Rocket Scientists guide. For the moment, these are just installation, design and configuration notes Daniel K. Schneider made while redesigning a simple directory to include information about people and things in our little organization. Many (short) explanations are just rephrasing or even cut/paste from their "attribution-noncommercial 2.2" licensed text.
Configuration notes for solaris 10
There is an OpenLDAP version included in a typical installation. You can find it here:
/opt/sfw /opt/sfw/sbin - links to binaries /opt/sfw/libexec - binaries /opt/sfw/etc/openldap - configuration files /opt/sfw/var/openldap-data - default data /opt/sfw/var/run - PID of the server
- Binaires in /opt/sfw/sbin
slapadd -> ../libexec/slapd slapcat -> ../libexec/slapd slapdn -> ../libexec/slapd slapindex -> ../libexec/slapd slappasswd -> ../libexec/slapd slaptest -> ../libexec/slapd slapcat -> ../libexec/slapd slapdn -> ../libexec/slapd slapindex -> ../libexec/slapd slappasswd -> ../libexec/slapd slaptest -> ../libexec/slapd
The configuration file
Location:
/opt/sfw/etc/openldap/sladpd.conf
You will have to define
- What schemas to load in
- Where datafiles and pidfile etc. go
- What users are allowed to do
Here is a fictional example (comments taken away from the original):
include /opt/sfw/etc/openldap/schema/core.schema include /opt/sfw/etc/openldap/schema/cosine.schema include /opt/sfw/etc/openldap/schema/inetorgperson.schema include /opt/sfw/etc/openldap/schema/nis.schema # Add your own include /opt/sfw/etc/openldap/schema/tecfa.schema pidfile /opt/sfw/var/run/slapd.pid argsfile /opt/sfw/var/run/slapd.args security ssf=1 update_ssf=112 simple_bind=64 # Access contol (this is too minimalistic) access to attr=userpassword by self write by anonymous auth access to * by self write by users read database bdb # Suffix and root dn, adjust to your own organization suffix "dc=tecfa, dc=unige, dc=ch" # Note: The root uid does not need to be in the LDAP database rootdn "uid=xxxx, dc=tecfa, dc=unige, dc=ch" rootpw secret directory /opt/sfw/var/openldap-data index objectClass eq
You may want to put the data and schema files in some other place than the default, since you may by mistake kill them after an upgrade of the system. e.g. I used /var/openldap instead of /open/sfw/var/
The Root DSE, suffix, naming context etc
- The LDAP protocol assumes there are one or more servers that jointly provide access to a Directory Information Tree (DIT).
- The Root DSE is the topmost (and "invisible" entry) in the LDAP hierarchy. This DSA-specific entry publishes information about the LDAP server including which LDAP versions it supports, any supported SASL mechanisms, supported controls as well as the DN for its subschemaSubentry. In addition to server information, operational attributes may be exposed that allow for extended administration functionality. Client software will usually access this kind of information. The root DSE (DSA-specific Entry) data can be retrieved from an LDAPv3 server by doing a base-level search with a null BaseDN and with filter ObjectClass=*.
- The Directory Information Tree (DIT), also called naming-context refers to the hierarchy of objects that make up the directory structure. More than one DIT may be supported by an LDAP server. A DIT or Naming Context defines a namespace within which information can be searched.
- The root, suffix or base refers to the topmost entry. In practical terms it's the name of the DIT and is part of every distinguised name.
- The rootdn is the id of the superuser. You have to define it and its password in the configuation file (see below).
- Directy System Agent" (DSA) means (basically) "LDAP server". It's X500 slang.
In practical terms, you have at some point to decide how to organize the structure of the LDAP. In a first version I had all objects under the root dn and the DIT had the simple suffix: "o=tecfa.unige.ch".
In a later version I decided to adopt a more common RFC 227 practise and to use the name of the principal web server as suffic. To do so, you must use "dc" (domain component) syntax:
E.g. a domain names like
tecfa.unige.ch
becomes
dc=tecfa,dc=unige,dc=ch
This has to be defined in the slapd.conf file as explained above
suffix "dc=tecfa, dc=unige, dc=ch"
And the super power user (name "xxxx" here), identified by "rootdn" becomes something like:
rootdn "uid=xxxx, dc=tecfa, dc=unige, dc=ch"
But bascially speaking you can do whater you like. Actually there is a second "standard". ISO suggested something like
ou=tecfa,o=unige,c=ch
Importing / exporting an LDIF file
To import an LDIF file:
/opt/sfw/sbin/slapadd -v -l your-ldif-file.ldif
To export an LDIF file:
/opt/sfw/sbin/slapcat > file_name.ldif
Warning: When I exported, then modified the structure (e.g. put people inside a ou), then imported ldifs again I noticed that I had to kill all (well some at least) attributes added by the server ! E.g.
entryUUID: .... entryCSN: ....
If I didn't kill these, I could find anything anymore. Maybe an import/export option I didn't get ...
To add or modify entries
ldapadd -D "cn=rootid,dc=tecfa,dc=unige,dc=ch -f addgroups.ldif -w secret
Change the rootid, the *.ldif file name and the secret password ... Also read the manual page about this. Adding entries that are really new is easy, but modifying old ones requires special ldif commands.
The difference (in a nutshell) between slapadd and ldapadd is that you use slapadd to create a new database (each time: stopping the server, kill all the database files, starting, importing) until you are happy. Once you like your LDAP, then you should use ldapadd to make changes or use some client software.
If you just make small changes, you can just use a GUI client to so. LDIF is never needed, it's just more practical for addition / modifcation of many entries. Some clients also have an ldif import button or even a cut/paste area. E.g. the rather nice phpLdapAdmin web client does.
Testing with a client
You can test your LDAP through an LDPA client like Apache Directory Studio. To connect to your LDAP server, make sure that the port is open both on your client machine and the server machine. By default LDAP uses port 389.
We suggest to install "Apache Directory Studio". To configure a connection to an LDAP server: Menu->LDAP->New connection
In the Authentication tab enter:
Bind DN or user: <the root dn you defined above>
Else, you may have the command line ldapsearch installed. More difficult, but a better bet for debugging.
Syntax for connecting as LDAP administrator:
ldapsearch -b <starting point> -D <user dn for connecting> -w - filter
Example: The following will display all attributes of the object "uid=joemiller" in a database that has root dn (suffix) of "c=tecfa,c=unige,c=ch" and people objects inside "ou=people". It will display all attributes for joe.
ldapsearch -b "ou=people,dc=tecfa,dc=unige,dc=ch" -D "uid=root,dc=tecfa,dc=unige,dc=ch" -w - "uid=joemiller"
Example: The following will search for "Daniel" in givenname(s) and display what it can to an anonymous bind (user). Notice that the people in the example below are just below the DIT root (good enough for a simple addressbook).
ldapsearch -b "o=tecfa.unige.ch" -v "givenname=Daniel"
- "b <starting point>" refers to the top of the tree you want to search. It must be a distinguished name, e.g. "ou=people,dc=example,dc=com". Starting points can be very different from server to server.
- "-w -" will have it prompt for a password.
- "-h host" allows to specify a host, by default it is localhost
- "-v will make verbose output (diagnostics).
The startup script
To start/stop automatically the server you can write a script like this, put it in /etc/init.d and then make links from /etc/rc3.d, /etc/rc0.d etc.
#!/bin/sh STARTCMD="/opt/sfw/libexec/slapd" LISTENPORTS="ldap:/// ldaps:///" STOPCMD="kill -INT `cat /var/openldap/run/slapd.pid`" # These are some string we reuse to give feedback. DESC="OpenLDAP standalone Deamon (slapd)" ERRORMSG="!!!! ERROR:" # This is used to see if slapd is running. Contains null if it doesn't # or a process description if it does. ISRUNNING=`cat /var/openldap/run/slapd.pid` # Now we check for the argument given on command line # and act unpon its value case "$1" in 'start') # We test if the server is not already running if [ -z "$ISRUNNING" ] ; then # We test if the server is effectively started when the command is issued. if $STARTCMD -h "$LISTENPORTS" ; then echo "$DESC started" ; else echo "$ERRORMSG $DESC could not be started" exit ; fi else echo "$ERRORMSG $DESC is already running" ; fi ;; 'stop') # We test if the server is already running if [ ! -z "$ISRUNNING" ] ; then # We test if the server is effectively stopped when the command is issued. if $STOPCMD ; then echo "$DESC stopped" ; else echo "$ERROR $DESC could not be stopped" ; fi else echo "$ERRORMSG $DESC is not running" exit ; fi ;; 'restart') # We test if the server is already running if [ ! -z "$ISRUNNING" ] ; then # We test if the server is effectively stopped when the command is issued. if $STOPCMD ; then # We test if the server is effectively started when the command is issued. if $STARTCMD -h "$LISTENPORTS" ; then echo "$DESC restarted" ; else echo "$ERRORMSG $DESC stopped but not restarted" exit ; fi else echo "$ERRORMSG $DESC could not be stopped (and hence not restarted)" exit ; fi else echo "$DESC is not running: Starting" ; if $STARTCMD -h "$LISTENPORTS" ; then echo "$DESC started" ; else echo "$ERRORMSG $DESC could not be started" exit ; fi fi ;; 'debug') # We test if the server is already running if [ ! -z "$ISRUNNING" ] ; then echo "Server was running: stopping" if $STOPCMD ; then echo "STOPPED" ; else echo "$ERRORMSG $DESC could not be stopped" exit ; fi fi # we look if a second argument is given for debug level if [ -z "$2" ] then LOGLEVEL="4095" ; else LOGLEVEL="$2" ; fi echo "attempting to start $DESC" echo "in debug mode with loglevel $LOGLEVEL" echo echo "terminal will remain open if it succeeds" echo "exit with CTRL-C" # Starting the server in debug mode if $STARTCMD -d $LOGLEVEL -h "$LISTENPORTS" ; then # this is a little tricky because the message outputs only # when Ctrl-C is issued. echo "$DESC STOPPED..." ; else echo echo echo "!!!!!!!!!!!!!!!!!!!!!!!!!!" echo "$ERRORMSG $DESC could not be started in debug mode" echo "$ERRORMSG or has exited abnormaly" echo "!!!!!!!!!!!!!!!!!!!!!!!!!!" echo "Check for error messages in the debug trace" echo exit ; fi ;; *) # We show how to use that script if the given argument is not recognized. echo "**************************************************************************" echo "* Usage : /etc/init.d/openldap.server {start|stop|restart [debug_level]} *" echo "**************************************************************************" ;; esac
See also on the Internet, e.g. OpenLDAP Start/stop script from LinAgora.org.
Installation notes for Ubuntu 4.1
OpenLDAP is distributed through the Synaptic Package Manager.
- The installer will ask the rootdn password.
- It also will automatically launch the LDAP server (use Menu System->Administration->Services to stop it again)
Configuration files are in:
/etc/ldap/
The rest is the same.
Operational attributes
There exist so-called operational attributes like "modifyTimestamp". They are not returned unless you specifically request it. You must also have the appropriate permission to view the attribute.
In OpenLDAP, you can ask for "+" to fetch all the operational attributes for an entry. That would look something like this:
ldapsearch -LLL -W -D "cn=Manager,dc=example,dc=com" -b "cn=Christoph,ou=People,dc=dexample,dc=com" "(objectClass=*)" '+'
Or just ask for the "modifyTimestamp" attribute by name. The "+" is similar in concept to the "*" which returns all the non-operational attributes.
Bascially speaking, an LDAP server will add attributes you didn't define. Some of these are useful in client applications, e.g. time stamps or who did what...
Organization of the directory tree
Basically, you should keep the structure simple and flat for the simple reason that moving around objects is much more complication than just changing an attribute. E.g. a student may become a teaching assistant and then a lecturer. So putting people in more than one group does not make sense in our case.
An example ldif for an extended address book
This start of an ldif file shows the following:
- The base naming context is dc=tecfa, dc=unige, dc=ch
The root (suffix) is defined like this:
dn: dc=tecfa, dc=unige, dc=ch dc: tecfa objectClass: dcObject objectClass: organization o: TECFA
All people go inside a single organizational unit: dn: ou=people, dc=tecfa, dc=unige, dc=ch. Else if you distinguish between students, teachers and so forth, you will have to move entries once persons change status (see also "Flat is Good").
In other words, each person has a dn like this:
dn: uid=xxxx, ou=people, dc=tecfa, dc=unige, dc=ch
dn: ou=people, dc=tecfa, dc=unige, dc=ch objectClass: top objectClass: organizationalUnit ou: people description: All people in the LDAP structuralObjectClass: organizationalUnit dn: uid=dksuid, ou=people, dc=tecfa, dc=unige, dc=ch objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson objectClass: tdsTecfaPerson givenName: Daniel sn: Schneider cn: Daniel Schneider uid: schneide description: Maitre d'enseignement et de recherche structuralObjectClass: tdsTecfaPerson tdsMemberCategory: TECFA bureau tdsMemberCategory: TECFA member tdsMemberCategory: TECFA teacher personalTitle: Dr. .... some entries deleted ....
One might also add an attribute that points from a user to one or more "ou"s (organizationalUnitName) and define several "organizationalUnit"s in order to "tag" people into categories (but so far I didn't). For the moment we just use some custom attributes and simple strings, but that's not as "safe" as design since in an LDAP client you could enter just anything into a "tdsMemberCategory" field. Note: giving people several "ou" attributes does not mean that they are in these "ou" subtrees. It's just a pointer to an "ou".
ou=teachers ou=students ou= ....
Before adding more trees and objects and relations that may go with it, you have to consider that this means writing more complicated code to edit or even just to display information. One reason to adopt LDAP for "information purposes" is that one can avoid the kind of horrible PHP programming one has to do with SQL databases;). With LDAP, you basically can just use an LDAP client to edit information and then produce some simple lists ....
Using LDAP to authenticate users in web portals
Probably it's a good idead to use the posixAccount which is in the nis.schema because one then also could use it for single sign-on login authentication (e.g. on various linux machines, samba, etc.) .... later maybe
Creating your own LDAP schema
Once you decided to adopt LDAP to manage information about users and other things in your organization, you will find out that the standardized schemas - although huge - are not enough.
So, in the slapd.conf file we added our own:
include /opt/sfw/etc/openldap/schema/tecfa.schema
- all entries start with "tds" in order to distinguish them a bit from the huge pile of existing ones
For instance, there is an attribute that allows to define a skill (e.g. "LDAP" or "knitting")
# Person's compentencies attributeType ( 1.3.6.1.4.1.11389.1.3.1.14 NAME 'tdsSkill' DESC 'Description of the persons skills/compentencies' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15)
See the LDAP article for some more information about schema writing.
Access control (ACL)
(not really complete)
OpenLDAP 2.0 contains two methods for specifying access control. The first is static, i.e. you define the rights in configuration files. wo other advantages of this method is that it should be more efficient in most cases and that the rules, being static, cannot be changed by external means using LDAP so it should be more secure. From an operational point of view, the problem of this method is that needs a server restart at every Access contol (ACL) change. (From the FAQ, Configuration / SLAPD Configuration)
The second method for access control inserts access control information inside the directory itself. Unfortunately, the standard for doing this in a way that is interoperable between servers of different vendors (this did not matter in the static config case) has not been finished and exists only as an Internet Draft (i.e. no RFC has been published and the specification might not even get enough consensus for an RFC to be published ever). (From the FAQ)
Via the old method (static)
access to <what> [ by <who> <access> [ <control> ] ]+
- Read 5.2 Securing the Directory (from the "Rocket scientist" manual).
- Read through the subcategories of More information about Access Control (scroll down the page and don't miss any links or examples ...)
Firstly you need to know about two principles:
- The general rule is: write special access rules first, generic access rules last.
- Never write more than a single rule about something. E.g. you can't have two rules rules that define acces to *.
Let's have a look at a few ACL patterns:
By default, anyone and everyone can read anything but but only the rootdn can make any updates (need to verify this). This implicit rule could be made explicit like this:
access to * by * read
First rule you may concerns the user passwords. Users can update but not read their password and anonymous users can authenticate (else no user can log in).
access to attrs=userpassword by self =xw by anonymous auth
"self" refers to the user.
The following rule means that the owners have full access to their entry and users can read everything. This is something you probably don't want, i.e. you might want to show some information (e.g. homepages or email addresses to a public at large (anonymous readers)
access to * by anonymous none by self write by users read
The following allows anonymous users to read Common Names (cn)
access to cn by anonymous read
Defining groups
One way to differentiate rights amoung the users is to create groups for them.
Firstly we define an object that represents a branch in the DIT within which we then can define various groups.
dn: ou=groups,dc=tecfa,dc=unige,dc=ch objectclass:organizationalunit ou: groups description: DIT tree for various groups
Then we can define groups inside. Here we define a "managers" group. I will have the right to change about everything in the DIT.
- It has the following dn (distinguished unique name) components:
- a name (the "cn=managers")
- the "ou=groups" will peut it inside the groups tree
- the global suffix for the LDAP naming context.
This object belongs to the class "groupOfNames" which is defined in both the core.schema and the cosine.scheme, e.g. schemas that you have by default in your LDAP. This class requires that you define at least the "cn" and that you have at least one "member" attribute. If find this a bit strange, e.g. at some point a group could be empty ...
dn: cn=managers,ou=groups,dc=tecfa,dc=unige,dc=ch objectclass:groupOfNames ou: groups cn: managers description: People that can change most entries in the LDAP (i.e. some TECFA employees). member: uid=miller,ou=people,dc=tecfa,dc=unige,dc=ch
Once you got such a group you then can give it rights in the slapd.conf file:
access to * by self write by group.exact="cn=managers,ou=groups,dc=tecfa,dc=unige,dc=ch" write by users read by * none
Access for php scripts and anymous users
I didn't find a satisfactory way to give read access to anonymous users
For PHP scripts, I just created a dummy user, that has full read access to everything (and write access to his own stuff which doesn't matter much). So even if by chance someone manages to get hold of the script code, he/she couldn't do much dammage since password access is more strict.
E.g. in the slapd.conf file I got something like this:
# Anonymous users are allowed access the dn base, else a client can't do anything access to dn.base="" by * read # Users can update but not read passwords, managers can do everything, anonymous can authenticate, # read access denied to all others access to attrs=userPassword by self =xw by anonymous auth by group.exact="cn=managers,ou=groups,dc=tecfa,dc=unige,dc=ch" write by * none # for all the rest, self can change all his stuff, managers can change everything, # users can read and guest can try to search (auth is included in this permission) access to * by self write by group.exact="cn=managers,ou=groups,dc=tecfa,dc=unige,dc=ch" write by users read by anonymous search
Bascially I can connect to the LDAP as anyomous user, but can't see nor list anything. Doesn't matter for now since I got this relatively safe solution for php scripts. After this LDAP only serves to manage some information for now.
You can see it here (well we have to update entries but that's another painful issue ...)
ACL with new method (inside the LDAP)
olcAccess: to <what> [ by <who> <accesslevel> <control> ]+
is a different way of doing it. You should read these olcAccess statements as ldif notation of an LDAP attribute.
- Read the Access Control Chapter of the manual.
- Type man slapd.access
Links
- OpenLDAP Software 2.0 - 2.4 Administrator's Guides
- tip: Use the 1HTML version to search through the page...
- Configuring slapd (version 2.3 on Feb 2008).
- LDAP Tutorial - Exercise with OpenLDAP v2.0.11 on Linux by P. Gietz and N. Klasen (2001)
- LDAP01, Sam Hart's classnotes