Background
We need a quick-and-dirty
Simplifications
Unlike other similar implementations out there, I have smaller goals:
- The database will be updated/written by the HL7 interface and web interface. The LDAP tools don’t need to write to it.
- All my user data should fit nicely into two tables (users and groups… and another table to relate many-to-many), and two Objectclasses in LDAPland.
Resources
These were unexpectedly hard to Google up, which is partly why I’m documenting them here for Posterity.
- Zytrax has an open source book about LDAP entitled LDAP for Rocket Scientists, which has an excellent chapter about LDAP concepts, which is nice since I don’t really understand LDAP like I do the other technologies involved.
- Section “10.10.2. back-sql Configuration” of the OpenLDAP manual
- The slapd-sql man page is indispensible. See especially the “METAINFORMATION USED” section.
- The README that comes with the back-sql samples
- The samples themselves (either unpack servers/slapd/back-sql/rdbms_depend/mysql from the source tarball or browse them, but you’ll probably want them unpacked so you can feed the schema to the mysql client. MOST OF WHAT YOU NEED TO LEARN is demonstrated in the samples.
- This 4-year-old blog article at/by “Flat Mountain” is sketchy but an improvement over the documents above!
- Albert Lash at DocuNext has a 2-year-old post similar to this one with another list of fine references!
- This HOWTO for OpenLDAP and Postgresql by Gilles Darold is far superior than the other articles, but needs adaptation of course for MySQL
- Likewise, here’s a good article for doing the same thing with Oracle/MSSQL
- And here are some extended notes from someone who did it with Oracle, including another explanation of the data mapping part.
I. Install Stuff
I’m using RHEL5 on a virtual machine. Your package system may vary. You may in fact have to compile from source to get SQL support in LDAP.
$ sudo yum install openldap openldap-clients openldap-servers openldap-devel openldap-servers-sql
II. Get and unpack the source code for the MySQL samples
- Download the source tarball to your machine
- Unpack it
tar zxf openldap-stable-20080813.tgz
III. Create the database and tables
Make sure the mysql server is running of course. We’re assuming it’s on the same host as the LDAP server.
Create the database (my_db_name) and a password-having user (my_mysql_user) with all privileges to that database (I recommend the MySQL Administrator GUI for this, strangely enough since I usually dislike clicking on stuff). You should be able to connect to your database like this now:
$ mysql -umy_mysql_user -pxxxxxxxxxxxxx my_db_nameBuild the tables that configure how OpenLDAP talks to the database.
- cd into servers/slapd/back-sql/rdbms_depend/mysql from the unpacked openldap source
- Feed backsql_create.sql to the ‘mysql’ client
$ mysql -umy_mysql_user -pxxxxxxxxxxxxx my_db_name < backsql_create.sql
IV. Hack the basic config files
a. slapd.conf
Basically I’m starting from the slapd.conf in the rdbms_depend/mysql sample (see above), but folding in some RedHatese file locations from the distributed slapd.conf.
I define an adminstrative user with the rootdn and rootpw directives. Use slappasswd to generate the hashed password:
$ slappasswd -h {crypt}
and type in the password. It will spit out the string you need, which is prefixed by the algorithm in curly braces.
# See slapd.conf(5) for details on configuration options. # This file should NOT be world readable. # include /etc/openldap/schema/core.schema include /etc/openldap/schema/cosine.schema include /etc/openldap/schema/inetorgperson.schema pidfile /var/run/openldap/slapd.pid argsfile /var/run/openldap/slapd.args # Timeout in seconds, 0 = never idletimeout 30 threads 32 # Debuging level, 0 = none loglevel 32 # THIS IS IMPORTANT-- without it you will get "<database> failed init (sql)!" # Load dynamic backend modules: modulepath /usr/lib/openldap # modules available in openldap-servers-sql RPM package: moduleload back_sql.la ####################################################################### # SQL Database Definitions ####################################################################### database sql suffix "ou=Junk,ou=AK,DC=mycompany,DC=com" rootdn "cn=root,ou=Users,ou=Junk,ou=AK,DC=,mycompany,DC=com" rootpw {CRYPT}xxxxxxxxxxxx # The name of the ODBC datasource (from odbc.ini, # not necessarily the database instance) to use. dbname my_database dbuser my_database_user dbpasswd xxxxxxxxxxxxxxxx # this is important because my simple view for the ldap_entries table # has no 'organization' object. See `man slapd-sql`. baseObject subtree_cond "ldap_entries.dn LIKE CONCAT('%',?)" insentry_stmt "INSERT INTO ldap_entries (dn,oc_map_id,parent,keyval) VALUES (?,?,?,?)" has_ldapinfo_dn_ru no ####################################################################### # Access Control # see http://www.openldap.org/doc/admin24/access-control.html # and `man slapd.access` ####################################################################### # Here we define who can see which parts of the directory # Hide the password attribute (except for the root user of course) but # allow authentication against it access to attrs=userPassword by anonymous auth by * none # don't allow anonymous access # allow authenticated users to see their own entry access to * by self read by * none
I started without the Access Control section and added it after I had things basically working.
b. ODBC config files
In my case they are in /etc/odbc.ini and /etc/odbcinst.ini already existed and just needed a MySQL section added to odbcinst.ini and a section for this particular database in odbc.ini, but I had to get the paths right for Red Hat. See the MySQL section of step 2 of the samples README document
/etc/odbcinst.ini:
# Driver from the MyODBC package # Setup from the unixODBC package [MySQL] Description = ODBC for MySQL Driver = /usr/lib/libmyodbc3.so # note, filename includes a 3, differs from docs Setup = /usr/lib/libodbcmyS.so FileUsage = 1
/etc/odbc.ini
[my_database] Description = my database Driver = MySQL Trace = No Database = my_db_name Servername = localhost UserName = my_database_user Password = xxxxxxxxxxxxxxxxx ReadOnly = No RowVersioning = No ShowSystemTables = No ShowOidColumn = No FakeOidIndex = No ConnSettings = SOCKET = /var/lib/mysql/mysql.sock
(OK, here’s where I dinked with the example database and read lots of examples and stuff, but hopefully you can skip that because of this excellent documentation
)
V. Create and populate your user and group tables
These are my basic tables of users and groups:
CREATE TABLE my_user ( id int(10) UNSIGNED NOT NULL AUTO_INCREMENT, username varchar(45) NOT NULL, password varchar(45) NOT NULL, first_name varchar(45) DEFAULT NULL, last_name varchar(45) DEFAULT NULL, PRIMARY KEY (id,username) ); CREATE TABLE my_group ( id int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `name` varchar(45) NOT NULL, PRIMARY KEY (id) ); CREATE TABLE my_group_member ( -- unique id isn't necessary but easier for symfony id int(10) UNSIGNED NOT NULL AUTO_INCREMENT, my_user_id int(10) UNSIGNED NOT NULL, my_group_id varchar(45) NOT NULL, PRIMARY KEY (id), UNIQUE KEY uniquepair (my_user_id,my_group_id) );
You’ll need to populate the tables with a user or two and at least one group, of course.
VI. Insert the special configuration rows
I entered these data sets using the MySQL Administrator GUI. The most helpful explanation was in the “METAINFORMATION USED” section of the slapd-sql man page.
Basically, the ldap_* tables show the SQL backend how to map the database tables onto LDAP objects. This is a nice reference to the common LDAP object schemas. That’s important because you can only map your database fields to fields in the schema for the objectClass you’re using (unless you want to start making your own schema, which is beyond my scope here). In my case I’m using inetOrgPerson, which inherits from organizationalPerson, which inherits from Person, and for groups, groupOfUniqueNames.
The ldap_entry_objclasses table needs one row for each objectClass we’re using:
| entry_id | oc_name |
|---|---|
| 1 | inetOrgPerson |
| 2 | groupOfUniqueNames |
The ldap_oc_mappings table also has a row for each objectClass, and it shows the sql backend how to find objects corresponding to those object classes.
| id | name | keytbl | keycol | create_proc | delete_proc | expect_return |
|---|---|---|---|---|---|---|
| 1 | inetOrgPerson | my_user | id | 0 | ||
| 2 | groupOfUniqueNames | my_group | id | 0 |
The ldap_attr_mappings table is the most complex part. Basically you’re showing the sql backend how to build queries that map the database columns onto LDAP attributes.
| id | oc_ | name | sel_ | sel_ | from_tbls | join_where | add_proc | delete_ | param_ | expect_ |
|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 1 | cn | concat( first_name, ‘ ‘, last_name ) | my_user | 3 | 0 | ||||
| 2 | 1 | givenName | first_name | my_user | 3 | 0 | ||||
| 3 | 1 | sn | last_name | my_user | 3 | 0 | ||||
| 4 | 1 | userPassword | password | my_user | 3 | 0 | ||||
| 5 | 1 | uid | username | my_user | 3 | 0 | ||||
| 7 | 2 | cn | name | my_group | 3 | 0 | ||||
| 8 | 2 | uniqueMember | dn | my_group, my_ | my_group.id = gu. | 3 | 0 |
Create a view for the DN s in ldap_entries
I’m going to use the username field in my DN instead of the common name (CN) in most examples, because it’s more guaranteed to be unique, and it is apparently OK to do so.
DROP TABLE ldap_entries; CREATE VIEW ldap_entries AS SELECT -- differentiate id spaces here (100000 + my_user.id) AS id, -- calculate a DN for each user ucase(concat( 'uid=', my_user.username, ',ou=Users,ou=Junk,ou=AK,DC=mycompany,DC=com' )) AS dn, -- the id of the inetOrgUser objectClass in ldap_entry_objclasses 1 AS oc_map_id, -- zero for every row -- (see baseObject in the `slapd-sql` man page) 0 AS parent, -- the real id in the user table my_user.id AS keyval FROM my_user UNION SELECT -- a different id space (200000 + my_group.id) AS id, -- calculate a DN for each object in our group table ucase(concat( 'cn=',my_group.name, ',ou=Users,ou=Junk,ou=AK,DC=mycompany,DC=com' )) AS dn -- the id of groupOfUniqueNames in ldap_entry_objclasses 2 AS oc_map_id, -- like above 0 AS parent, my_group.id AS id FROM my_group
VII. Test and stuff
Launch the Slap daemon
It’s nice to launch slapd in debug mode in one terminal. This is pretty verbose and will hopefully tell you if you’ve got something wrong in your mapping tables or anything:
sudo /usr/sbin/slapd -d 5
Test connectivity and data with an LDAP client
Hint: it would help to LEARN HOW TO USE ldapsearch correctly.
Connect anonymously and spit out all the LDAP data (if ACL allows it– by slapd.conf above allows anonymous connections but won’t show any of the data):
ldapsearch -x -s sub -b "ou=Junk,ou=AK,DC=mycompany,DC=com" "(objectClass=*)"
Connect as root (the special account defined in slapd.conf) and show all the data in the directory:
ldapsearch -x -D uid=root,ou=USERS,OU=JUNK,OU=AK,DC=MYCOMPANY,DC=COM -w xxxxxxxxx -s sub -b "ou=Junk,ou=AK,DC=mycompany,DC=com" "(objectClass=*)"
Here’s an example of what it shows me:
# extended LDIF # # LDAPv3 # base <ou=Junk,ou=AK,DC=mycompany,DC=com> with scope subtree # filter: (objectClass=*) # requesting: ALL # # Junk, AK, mycompany.com dn: ou=Junk,ou=AK,dc=mycompany,dc=com objectClass: extensibleObject description: builtin baseObject for back-sql description: all entries mapped in the "ldap_entries" table description: must have "0" in the "parent" column ou: Junk # TESTUSER, USERS, JUNK, AK, MYCOMPANY.COM dn: uid=TESTUSER,ou=USERS,OU=JUNK,OU=AK,DC=MYCOMPANY,DC=COM objectClass: inetOrgPerson cn: test user ou: guest sn: user uid: testuser givenName: test userPassword:: dGVzdHBhc3M= # 1234567890, USERS, JUNK, AK, MYCOMPANY.COM dn: uid=1234567890,ou=USERS,ou=JUNK,ou=AK,DC=MYCOMPANY,DC=COM objectClass: inetOrgPerson cn: test2 user2 ou: guest sn: user2 uid: 1234567890 givenName: test2 userPassword:: MTIzNDU2Nzg5MA== ... # VALIDUSERS, GROUPS, JUNK, AK, MYCOMPANY.COM dn: cn=VALIDUSERS,ou=GROUPS,ou=JUNK,ou=AK,DC=MYCOMPANY,DC=COM objectClass: groupOfUniqueNames cn: validUsers uniqueMember: uid=1234567890,ou=USERS,OU=JUNK,OU=AK,DC=MYCOMPANY,DC=COM ... uniqueMember: uid=TESTUSER,ou=USERS,OU=JUNK,OU=AK,DC=MYCOMPANY,DC=COM # search result search: 2 result: 0 Success # numResponses: 8 # numEntries: 7
Connect as a valid user and ask for everything, but according to my ACLs the user can only see itself:
ldapsearch -x -D uid=TESTUSER,ou=USERS,OU=JUNK,OU=AK,DC=MYCOMPANY,DC=COM -w testpass -s sub -b "ou=Junk,ou=AK,DC=mycompany,DC=com" "(objectClass=*)"
Additional Considerations
chkconfig is the Red Hat tool for managing init scripts– here’s how to make sure the slapd starts at boot time.
$ sudo chkconfig --add ldap $ sudo chkconfig ldap on
Ah, Mr. Flat Mountain reminds,
9) Ports for the Firewall LDAP runs on port 389/tcp by default and LDAP over SSL is 636/tcp.
Securing communication between the LDAP client and OpenLDAP server with SSL/TLS is beyond my scope here, but it seems to be much better documented.
Good luck!
Logging for successes and failures:
I only care about success and failure, not exhaustive debugging in the real log.
add this line to /etc/syslog.conf, so that stuff will actually go to a log, /var/log/slapd.log:
Turn the log level to 256 in /etc/openldap/slapd.conf
loglevel 256Restart slapd and syslogd:
This line indicates an authentication failure: