Pages

Friday, December 23, 2011

LDAP for Solaris 10

With this article I want you to show how to set up OpenLDAP for Solaris 10. I have here a small Sun Fire V100 with 2GB running - perfect for playing around. Using Solaris as a LDAP client is a little bit strange first, but with the time you will enjoy it. Solaris comes with a tool called ldapclient to initiate a Solaris host as a LDAP client. There is a daemon called ldap_cachemgr which will be started when you initialize Solaris as a LDAP client and it runs all the time.
The LDAP server has to provide at least on special profile for Solaris. To provide a profile for Solaris you have to add two extra schema files which don't come with OpenLDAP but can be easily downloaded. As mentioned, I'm going to show you how to setup OpenLDAP as a LDAP server for Solaris. The server itself will act as a client too. Before you can use OpenLDAP with Solaris you have to download Berkeley DB from http://www.oracle.com (just like nearly every DB these days). I am using nearly the same releases used in Slackware 13.37, they just work. Also the compile instructions are very similar and highly inspired by the Slackware build scripts. After you have downloaded the Berkeley DB, copy it to /usr/src and extract it:

# cd /usr/src
# gzip -dc db-4.4.20.tar.gz | tar xf -

Then go to the new directory and create a build directory where you can compile the source:

# cd /usr/src/db-4.4.20
# mkdir build && cd build

Export the following PATH variable to include most compiler tools in your path and compile the DB:

# export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/sfw/bin:/usr/sfw/sbin:/usr/ccs/bin
# ../dist/configure --prefix=/opt/db/4.4.20 --enable-shared --enable-rpc --enable-cxx --enable-compat185
...
# make && make install
...

The binaries and libraries are now available in /opt/db/4.4.20. Go to /opt/db and create a symbolic link from 4.4.20 to latest:

# cd /opt/db/
# ln -s 4.4.20 latest

So far so good, the DB is compiled. Next you need OpenLDAP it self. Download it and extract it:

# cd /usr/src
# wget ftp://ftp.openldap.org/pub/OpenLDAP/openldap-release/openldap-2.4.26.tgz
...
# gzip -dc openldap-2.4.26.tgz | tar xf -

Go to the new directory, export all variables and compile the source:

# cd openldap-2.4.26
# export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/sfw/bin:/usr/sfw/sbin:/usr/ccs/bin
# export CPPFLAGS="-I/opt/db/latest/include/"
# export LD_LIBRARY_PATH=/opt/db/4.4.20/lib:$LD_LIBRARY_PATH
# ./configure --prefix=/opt/openldap/2.4.26 --with-cyrus-sasl --with-tls --enable-crypt --with-threads --enable-debug --enable-syslog --enable-dynamic --enable-bdb --enable-local --enable-proctitle --disable-static --enable-shared --enable-slapd
...
# make
...
# make install
...

After the compile has finished, go to /opt/openldap and create a symbolic link to 2.4.26:

# cd /opt/openldap
# ln -s 2.4.26 latest

Next configure your LDAP. Go to the LDAP configuration file, make a backup of the original configuration file and create a new one:

# cd /opt/openldap/latest/etc/openldap
# mv slapd.conf slapd.conf.orig
# vi slapd.conf
# SCHEMES
include         /opt/openldap/latest/etc/openldap/schema/core.schema
include         /opt/openldap/latest/etc/openldap/schema/cosine.schema
include         /opt/openldap/latest/etc/openldap/schema/inetorgperson.schema
include         /opt/openldap/latest/etc/openldap/schema/nis.schema

# ACL
include         /opt/openldap/latest/etc/openldap/acl.conf

# DATABASE
database        bdb
directory       /var/lib/ldap/example.com

# GENERIC
pidfile         /var/run/slapd.pid
argsfile        /var/run/slapd.args
suffix          "dc=example,dc=com"
rootdn          "cn=ldapadmin,dc=example,dc=com"
rootpw          {SSHA}tJMqZ3lE22tS6ygBbexB9yiCT0ebfSXf
password-hash   {CRYPT}
loglevel        256
sizelimit       unlimited

The section SCHEMES will load some definitions for the informations you want to provide. There are various schemes available depending on the informations you want to provide in your environment. For a proper use with Solaris we need to expand them later.
The section ACL will load the access control list. You can put it directly into the configuration file or put in a seperate file. I like to store my ACL's in a seperate file:

# vi /opt/openldap/latest/etc/openldap/acl.conf
access to attrs=userPassword
    by dn.base="uid=proxy,dc=example,dc=com" read
    by self write
    by * auth

access to dn.base=""
    by * read

access to * by self write
    by * read

They are read from the bottom to the top, at first everybody gets read access to everything, but they are getting more granular when it's about passwords at the top. With my example DIT (later) I will create a simple security object as proxy user. This is needed to read the passwords for authentication under Solaris.
The section DATABASE defines the database and where it will be stored, in this case a simple BDB. When you start slapd (the OpenLDAP daemon) then you may notice a message like this in the logs:

# cat /var/log/ldap.log
...
bdb_db_open: warning - no DB_CONFIG file found in directory /var/lib/ldap/example.com: (2). Expect poor performance for suffix "dc=example,dc=com".
...

You can get rid of this message when you create the DB_CONFIG file. Just copy the one found in the openldap package:

# mkdir -p /var/lib/ldap/example.com
# cp /opt/openldap/latest/var/openldap-data/DB_CONFIG.example /var/lib/ldap/example.com/DB_CONFIG

The last section is the GENERIC section. It holds some informations for the LDAP daemon (slapd) itself like the pidfile, the suffix (eg. your domain) etc. For the above defined pid file you have to create a directory:

# mkdir /var/run/slapd/

The sections above are not for real - I just like to use them to make the configuration file easy to understand.
Very important is the rootpw. The rootpw has to be generated by slappasswd:

# /opt/openldap/latest/sbin/slappasswd
New password:
Re-enter new password:
{SSHA}tJMqZ3lE22tS6ygBbexB9yiCT0ebfSXf

Put the complete generated string into the configuration file slapd.conf including the start of the string {SSHA}.
Before you start slapd you should configure syslog for logging. Add the following lines to your /etc/syslog.conf. Important note: the spaces between local4.* and /var/log/ldap.log aren't spaces - they have to be tabs! Otherwise your syslog daemon not log correct:

# vi /etc/syslog.conf
...
# LDAP
local4.debug <- tab -> ifdef(`LOGHOST', /var/log/ldap.log, @loghost)
...

slapd will log on local4, with the loglevel 256 defined you will be able to see most information about connections, filters etc. Finally create the logfile and restart the syslog daemon:

# touch /var/log/ldap.log
# chmod 644 /var/log/ldap.log
# chown root:sys /var/log/ldap.log
# svcadm restart svc:/system/system-log:default

Next download OpenDS. OpenDS is a Open Directory Service written in Java (you can use also OpenDS direclty as LDAP server). It contains a few additional schema files which we need before we can continue:

# cd /usr/src
# wget -c "http://java.net/downloads/opends/promoted-builds/2.2.1/OpenDS-2.2.1.zip"
...


Unzip the package:

# unzip OpenDS-2.2.1.zip
...


Create a new subdirectory in your schema directory for the solaris schema files:

# mkdir -p /opt/openldap/latest/etc/openldap/schema/solaris
And change into the directory that comes with OpenDS schema files:

# cd /usr/src/OpenDS-2.2.1/config/schema/

The shipped ldif files that comes with the OpenDS package are not ready for OpenLDAP. They have a little different layout which can be changed with grep, ggrep and sed to make the ldif files usable for OpenLDAP:

# grep -v "^#" 05-rfc4876.ldif | grep -v "dn: cn=schema" | grep -v "objectClass: top" | grep -v "objectClass: ldapSubentry" | grep -v "objectClass: subschema" | sed 's/attributeTypes:/attributeType/g' | sed 's/objectClasses:/objectClass/g' > /opt/openldap/latest/etc/openldap/schema/solaris/05-rfc4876.schema
# ggrep -A2 "1.3.6.1.1.1.1.30" 04-rfc2307bis.ldif | sed 's/attributeTypes:/attributeType/g' > /opt/openldap/latest/etc/openldap/schema/solaris/04-rfc2307bis.schema
# ggrep -A2 "1.3.6.1.1.1.2.15" 04-rfc2307bis.ldif | sed 's/objectClasses:/objectClass/g' >> /opt/openldap/latest/etc/openldap/schema/solaris/04-rfc2307bis.schema


Execute the complete above commands to turn the shipped ldif file into a schema file. Then add the new schema files to your slapd.conf:

# cd /opt/openldap/latest/etc/openldap
# vi slapd.conf
# SCHEMES
...
# SOLARIS
include         /opt/openldap/latest/etc/openldap/schema/solaris/05-rfc4876.schema
include         /opt/openldap/latest/etc/openldap/schema/solaris/04-rfc2307bis.schema


...

Exit vi and export the LD_LIBRARY_PATH variable and try to start slapd. Remember that you have to export LD_LIBRARY_PATH with the Berkeley DB libraries once before you can start slaps:

# export LD_LIBRARY_PATH=/opt/db/4.4.20/lib:$LD_LIBRARY_PATH
# /opt/openldap/latest/libexec/slapd -h ldap://192.168.1.75:389

You should see some information in ldap.log now:

# tail -f /var/log/ldap.log
Nov 13 18:54:14 bck01 slapd[13558]: [ID 100111 local4.debug] slapd starting
Nov 13 18:54:14 bck01 slapd[13557]: [ID 702911 local4.debug] @(#) $OpenLDAP: slapd 2.4.26 (Nov 13 2011 18:13:56) $
Nov 13 18:54:14 bck01   root@bck01:/usr/share/src/openldap-2.4.26/servers/slapd
Nov 13 18:54:14 bck01 slapd[13558]: [ID 468869 local4.debug] bdb_monitor_db_open: monitoring disabled; configure monitor database to enable
Nov 13 18:54:14 bck01 slapd[13558]: [ID 100111 local4.debug] slapd starting
...

Now you need to create a basic DIT (domain information tree) to store the users, passwords, groups and a simple profile:

# vi basic_dit.ldif
dn: dc=example,dc=com
o: example
dc: example
objectClass: dcObject
objectClass: organization
objectClass: nisDomainObject
nisDomain: example.com

dn: ou=profile,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: profile

dn: cn=default,ou=profile,dc=example,dc=com
objectClass: DUAConfigProfile
cn: default
defaultSearchBase: dc=example,dc=com
credentialLevel: anonymous
authenticationMethod: none
defaultSearchScope: sub
profileTTL: 300
searchTimeLimit: 60
defaultServerList: 192.168.1.75
serviceSearchDescriptor: passwd: ou=users,dc=example,dc=com?one
serviceSearchDescriptor: shadow: ou=users,dc=example,dc=com?one
serviceSearchDescriptor: group: ou=groups,dc=example,dc=com?one

dn: ou=groups,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: groups

dn: cn=staff,ou=groups,dc=example,dc=com
gidNumber: 10
cn: staff
objectClass: posixGroup
objectClass: top

dn: ou=users,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: users

dn: uid=sneill,ou=users,dc=example,dc=com
cn: Sam Neill
sn: Neill
givenName: Sam
uid: sneill
uidNumber: 1000
gidNumber: 10
homeDirectory: /home/sneill
loginShell: /bin/bash
gecos: Normal User
mail: sam.neill@example.com
shadowMax: 45
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
userPassword:

dn: uid=proxy,dc=example,dc=com
objectClass: account
objectClass: simpleSecurityObject
objectClass: top
userPassword:
uid: proxy

The basic dit above will add your domain (example.com), creates three organizational units (groups, users and profile) and creates a group (staff with gid 10), a user (sneill with uid 1000), a default profile and a proxy user. Now add the basic dit to your LDAP:

# /opt/openldap/latest/bin/ldapadd -x -W -D 'cn=ldapadmin,dc=example,dc=com' -h 192.168.1.75 -f basic_dit.ldif
Enter LDAP Password:
adding new entry "dc=example,dc=com"

adding new entry "ou=profile,dc=example,dc=com"

adding new entry "cn=default,ou=profile,dc=example,dc=com"

adding new entry "ou=groups,dc=example,dc=com"

adding new entry "cn=staff,ou=groups,dc=example,dc=com"

adding new entry "ou=users,dc=example,dc=com"

adding new entry "uid=sneill,ou=users,dc=example,dc=com"

adding new entry "uid=proxy,dc=example,dc=com"

When you did it so far then the LDAP server it self is running. Note that the password for the user sneill is not set yet, you can do this easily with ldappasswd:

# /opt/openldap/latest/bin/ldappasswd -x -W -D 'cn=ldapadmin,dc=example,dc=com' -h 192.168.1.75 -S 'uid=sneill,ou=users,dc=example,dc=com'
New password:
Re-enter new password:
Enter LDAP Password:

The first password you enter is for the user sneill, the second to verify the first password. The third password you have to enter is for the user ldapadmin to authenticate against the LDAP. Do the same for the proxy user:

# /opt/openldap/latest/bin/ldappasswd -x -W -D 'cn=ldapadmin,dc=example,dc=com' -h 192.168.1.75 -S 'uid=proxy,dc=example,dc=com'
New password:
Re-enter new password:
Enter LDAP Password:

Now set the domainname. The domainname has to be the same as the domainname in the nisDomain attribute from the above basic dit, in this case example.com. Keep in mind that the NIS domain name has nothing to do with your real domain name. In my case both are the same, but your real domain name could be example.com while the NIS domain name could be foobar:

# domainname example.com

And finally configure your system for LDAP:

# ldapclient -v manual -a credentialLevel=proxy -a authenticationMethod=simple -a proxyDN=uid=proxy,dc=example,dc=com -a proxyPassword=It'satrap! -a defaultServerList=192.168.1.75 -a defaultSearchBase=dc=example,dc=com -a serviceSearchDescriptor=passwd:ou=users,dc=example,dc=com?one -a serviceSearchDescriptor=group:ou=groups,dc=example,dc=com?one
...
restart: milestone/name-services:default... success
System successfully configured

Don't forget to set the right password for the proxy user in the ldapclient command option list. Now play a little bit with one of the Solaris native LDAP tools - ldaplist. With ldaplist you can list your dit right from your server without using the ldapsearch tool:

# ldaplist
dn: ou=profile,dc=example,dc=com

dn: ou=groups,dc=example,dc=com

dn: ou=users,dc=example,dc=com

dn: uid=proxy,dc=example,dc=com

# ldaplist -l
dn: ou=profile,dc=example,dc=com
        objectClass: organizationalUnit
        objectClass: top
        ou: profile

dn: ou=groups,dc=example,dc=com
        objectClass: organizationalUnit
        objectClass: top
        ou: groups

dn: ou=users,dc=example,dc=com
        objectClass: organizationalUnit
        objectClass: top
        ou: users

dn: uid=proxy,dc=example,dc=com
        objectClass: account
        objectClass: simpleSecurityObject
        objectClass: top
        uid: proxy
        userPassword: {CRYPT}O6raWkfPSufks

The next ldaplist command sample is important:

# ldaplist -vl passwd
+++ database=passwd
+++ filter=objectclass=posixaccount
+++ template for merging SSD filter=%s
dn: uid=sneill,ou=users,dc=example,dc=com
        cn: Sam Neill
        sn: Neill
        givenName: Sam
        uid: sneill
        uidNumber: 1000
        gidNumber: 10
        homeDirectory: /home/sneill
        loginShell: /bin/bash
        gecos: Normal User
        mail: sam.neill@example.com
        shadowMax: 45
        objectClass: top
        objectClass: person
        objectClass: organizationalPerson
        objectClass: inetOrgPerson
        objectClass: posixAccount
        objectClass: shadowAccount
        userPassword: {CRYPT}SM2kt6IBQlP9k

As long as you don't see the userPassword attribute in the above ldaplist command your users won't be able to authenticate to the system! I have seen a lot of threads, wikis etc struggling with this - I ran into the same issue and solved it by creating the proxy user and give him read rights to the userPassword attribute inside the ACL.
You can also use the native ldapsearch command instead of the compiled OpenLDAP ldapsearch command, but the syntax is a little bit different. Enclosed an example how to use the native ldapsearch and the OpenLDAP ldapsearch command:

# /bin/ldapsearch -v -h 192.168.1.75 -p 389 -D 'cn=ldapadmin,dc=example,dc=com' -w - -b 'dc=example,dc=com' -s base '(&(objectClass=nisDomainObject)(nisDomain=example.com))'
Enter bind password:
ldapsearch: started Sun Nov 20 19:47:06 2011

ldap_init( 192.168.1.75, 389 )
filter pattern: (&(objectClass=nisDomainObject)(nisDomain=example.com))
returning: ALL
filter is: (&(objectClass=nisDomainObject)(nisDomain=example.com))
version: 1
dn: dc=example,dc=com
dc: example
nisDomain: example.com
o: Example
objectClass: dcObject
objectClass: organization
objectClass: nisDomainObject
1 matches

# /opt/openldap/latest/bin/ldapsearch -h 192.168.1.75 -p 389 -x -W -D 'cn=ldapadmin,dc=example,dc=com' -b 'dc=example,dc=com' -s base '(&(objectClass=nisDomainObject)(nisDomain=example.com))'
Enter LDAP Password:
# extended LDIF
#
# LDAPv3
# base <dc=example,dc=com> with scope baseObject
# filter: (&(objectClass=nisDomainObject)(nisDomain=example.com))
# requesting: ALL
#

# example.com
dn: dc=example,dc=com
dc: example
nisDomain: example.com
o: Example
objectClass: dcObject
objectClass: organization
objectClass: nisDomainObject

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

Now test if everything works with getent and check for users and groups:

# getent passwd
...
sneill:x:1000:10:Normal User:/home/sneill:/bin/bash
# getent group
...
staff::10:

The group staff should appear twice - one from your file based group entry in /etc/group and one from your LDAP entry. Now create a home directory for sneill:

# mkdir -p /home/sneill
# chown -R sneill:staff /home/sneill

And try to su:

# su - sneill
$ id -a
uid=1000(sneill) gid=10(staff) groups=10(staff)
$ exit
logout

At this point your LDAP server is running and usable with Solaris. Everytime you want to add a user take the template from the basic dit above, add it to the dit, create a password and create a home directory. Try to login via ssh:

# ssh sneill@192.168.1.75

Every user can change now their password with the passwd command:

$ passwd -r ldap
passwd: Changing password for sneill
Enter existing login password:
New Password:
Re-enter new Password:
passwd: password successfully changed for sneill

The above method to initialize a Solaris host as a LDAP client has several disadvantages:

1. unneccassary long ldapclient command
2. a extra proxy user

Lets try to make it a little easier. First remove the proxy user from the LDAP ACL and give read permission to the userPassword attribute to everyone:

# cd /opt/openldap/latest/etc/openldap
# vi acl.conf
access to attrs=userPassword
    by self write
    by * read
    by * auth
...

This does not effect your security because the ldaplist command can be executed by any user. So every user can read your encrypted password. In this case it is OK to give read access to everyone. To activate the new ACL restart slapd:

# kill -15 `cat /var/run/slapd.pid`
# export LD_LIBRARY_PATH=/opt/db/4.4.20/lib:$LD_LIBRARY_PATH
# /opt/openldap/latest/libexec/slapd -h ldap://192.168.1.75:389

Next run a much simplier ldapclient command:

# ldapclient -v init 192.168.1.75
Arguments parsed:
        defaultServerList: 192.168.1.75
...
restart: milestone/name-services:default... success
System successfully configured

All information are already stored in the profile which is defined in the LDAP itself, no more options needed. This is much easier then initializing a Solaris host manually. You can savely remove the proxy user from your dit now:

# /opt/openldap/latest/bin/ldapdelete -x -W -D 'cn=ldapadmin,dc=example,dc=com' -h 192.168.1.75 'uid=proxy,dc=example,dc=com'
Enter LDAP Password:

And test that you can see the userPassword attribute when executing the ldaplist command:

# ldaplist -l passwd
dn: uid=sneill,ou=users,dc=example,dc=com
        cn: Sam Neill
        sn: Neill
        givenName: Sam
        uid: sneill
        uidNumber: 1000
        gidNumber: 10
        homeDirectory: /home/sneill
        loginShell: /bin/bash
        gecos: Normal User
        mail: sam.neill@example.com
        shadowMax: 45
        objectClass: top
        objectClass: person
        objectClass: organizationalPerson
        objectClass: inetOrgPerson
        objectClass: posixAccount
        objectClass: shadowAccount
        userPassword: {crypt}Skp/ZPmPGbkAw

Hope that this article cleared up a few things how to set up OpenLDAP for use with Solaris.

Links:
http://dsc.sun.com/solaris/articles/user_auth_solaris1.html
http://www.brandonhutchinson.com/wiki/Main_Page#LDAP
http://opends.java.net/

Update 11/30/2012: Replaced the two solaris schema files with ldif files from OpenDS