Monday, October 31, 2005

Configuring Apache 2 and Tomcat 5.5 with mod_jk

Update May 15th 2007

If you're interested in setting up Apache virtual hosts with Tomcat 5.5 and mod_jk, check out my recent blog post on that subject.

I recently went through the painful exercise of configuring Tomcat 5.5 behind Apache 2 using the mod_jk connector. I had done it before with mod_jk2, but it seems that mod_jk2 is deprecated, so I wanted to redo it with the officially supported mod_jk connector. Although I found plenty of tutorials and howtos on Google, they all missed some important details or were not exactly tailored to my situation. So here's my own howto:

Step 1: Install Apache 2

I won't go into many details, as this a very well documented process. I installed httpd-2.0.55 and I used the following configuration options:

./configure --enable-so --enable-mods-shared=most

In the following discussion, I will assume that Apache 2 is installed in /usr/local/apache2.

Step 2: Install JDK 1.5

In my case, I put the JDK files in /usr/local/java and I added this line to root's .bash_profile file:

export JAVA_HOME=/usr/java/jdk1.5.0_05

Step 3: Install Tomcat 5.5

Download apache-tomcat-5.5.12.tar.gz and put it in /usr/local. Unpack the package, create tomcat user and group, change ownership:

# cd /usr/local; tar xvfz apache-tomcat-5.5.12
# ln -s /usr/local/apache-tomcat-5.5.12 /usr/local/tomcat
# groupadd tomcat; useradd tomcat -g tomcat -d /usr/local/tomcat tomcat
# chown -R tomcat.tomcat /usr/local/apache-tomcat-5.5.12 /usr/local/tomcat

Modify .bash_profile for user tomcat and source it

Become user tomcat and modify .bash_profile file by adding following lines:

export PATH=$PATH:/usr/local/bin:/usr/local/tomcat/bin
export JAVA_HOME=/usr/java/jdk1.5.0_05
export CATALINA_HOME=/usr/local/tomcat

$ . ~/.bash_profile

Test starting/stopping the Tomcat server

Try to start up tomcat by running startup.sh (it's in bin subdir, should be in PATH)

If you do ps -def | grep tomcat you should see something like:

tomcat 18591 1 88 06:40 pts/0 00:00:02 /usr/java/jdk1.5.0_05/bin/java -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djava.util.logging.config.file=/usr/local/tomcat/conf/logging.properties -Djava.endorsed.dirs=/usr/local/tomcat/common/endorsed -classpath :/usr/local/tomcat/bin/bootstrap.jar:/usr/local/tomcat/bin/commons-logging-api.jar -Dcatalina.base=/usr/local/tomcat -Dcatalina.home=/usr/local/tomcat -Djava.io.tmpdir=/usr/local/tomcat/temp org.apache.catalina.startup.Bootstrap start

Try to shut down tomcat by running shutdown.sh (it's in bin subdir, should be in PATH)

If you do ps -def | grep tomcat you shouldn't see the above process running.

Step 4: Install the mod_jk connector

Download jakarta-tomcat-connectors-1.2.14.1-src.tar.gz and unpack it, then configure and build it:

# cd jakarta-tomcat-connectors-1.2.14.1-src/jk/native
# ./buildconf.sh
# ./configure --with-apxs=/usr/local/apache2/bin/apxs
# make; make install

Verify that mod_jk.so is in /usr/local/apache2/modules and is chmod 755.

Step 5: Connect Tomcat to Apache

Create workers.properties file in /usr/local/apache2/conf with following contents:

#
# This file provides minimal jk configuration properties needed to
# connect to Tomcat.
#
# We define a worked named 'default'
#

workers.tomcat_home=/usr/local/tomcat
workers.java_home=/usr/java/jdk1.5.0_05
ps=/
worker.list=default

worker.default.port=8009
worker.default.host=localhost
worker.default.type=ajp13
worker.default.lbfactor=1

Edit httpd.conf and add the following mod_jk-specific directives (I added them just before the start of Section 3 / Virtual Hosts).

Important note: the name of the worker defined in the workers.properties file ('default' in this example) needs to be the same as the worker that appears in httpd.conf in the JkMount lines. Also note that the JkMount lines below only map the two sample JSP/servlet applications that ship with Tomcat. You need to add similar lines for your custom application.


#
# Mod_jk settings
#
# Load mod_jk module
LoadModule jk_module modules/mod_jk.so
# Where to find workers.properties
JkWorkersFile conf/workers.properties
# Where to put jk logs
JkLogFile logs/mod_jk.log
# Set the jk log level [debug/error/info]
JkLogLevel debug
# Select the log format
JkLogStampFormat "[%a %b %d %H:%M:%S %Y] "
# JkOptions indicate to send SSL KEY SIZE,
JkOptions +ForwardKeySize +ForwardURICompat -ForwardDirectories
# JkRequestLogFormat set the request format
JkRequestLogFormat "%w %V %T"
# Send JSPs for context /jsp-examples to worker named default
JkMount /jsp-examples/*.jsp default
# Send servlets-examples to worker named default
JkMount /servlets-examples/* default

Keep editing httpd.conf and add following Alias directives (for example under the entry for the icon Alias). These directives tell Apache to map /jsp-examples and servlets-examples to the sample directories that ship with Tomcat.

# Static files in the jsp-examples webapp are served by apache
Alias /jsp-examples "/usr/local/tomcat/webapps/jsp-examples/"

Options FollowSymLinks
AllowOverride None
Allow from all


# The following line prohibits users from directly access WEB-INF

AllowOverride None
deny from all


# Static files in the servlets-examples webapp are served by apache
Alias /servlets-examples "/usr/local/tomcat/webapps/servlets-examples/"

Options FollowSymLinks
AllowOverride None
Allow from all


# The following line prohibits users from directly access WEB-INF

AllowOverride None
deny from all


Restart Apache via /etc/rc.d/init.d/httpd restart

Test standalone Tomcat server by going to http://Web_server_name_or_IP:8080

Test Apache/Tomcat integration by going to http://Web_server_name_or_IP/jsp-examples and http://Web_server_name_or_IP/servlets-examples

That should be it. At least it did the trick for me. In future posts I'll cover Tomcat clustering/session replication and some tips for tuning Apache/Tomcat.

Helpful articles/howtos/tutorials:

A. Ramnishath's Knowledge Base: Configuring AJP1.3 Connector for Tomcat
Jakarta Tomcat Connector -- Apache Howto
JSP Quick-Start for Linux

Friday, October 21, 2005

Mailing lists for Cheesecake project at SourceForge

At Titus's prompting (he challenged me to be a Real Man and not use wimpy Forums but Mailing Lists), I created two mailing lists for the Cheesecake project at SourceForge.net: cheesecake-devel and cheesecake-users. Feel free to check them out and contribute if you're interested in this project.

Proper location for LICENSE, CHANGELOG and other files?

What do people think should be the proper location for files such as LICENSE, ANNOUNCE, CHANGELOG, README?

Some projects have them in the top-level directory, some have them in a sub-directory such as 'docs'. Currently the Cheesecake index penalizes projects that do not have these files in the top-level project directory.

If you have any ideas, please leave a comment here or, even better, post to this Cheesecake Open Discussion thread.

Update: I also created two mailing lists for the Cheesecake project at SourceForge.net: cheesecake-devel and cheesecake-users.

Wednesday, October 19, 2005

Recommended blog: Maeda's 'Simplicity'

"Thoughts on Simplicity" is the blog of John Maeda, a professor at the MIT Media Lab. I've been reading it for a couple of months and I always take away intriguing ideas, especially about how to strive for simplicity and elegant design in our cluttered and complex world.

Maeda periodically posts his Laws of Simplicity and he says he'll stop the blog when he'll reach the sixteenth. He's now up to ten. Here is Maeda's Tenth Law of Simplicity:

Less breeds less; more breeds more.
Equilibrium is found at many
points between less and more,
but never nearest the extrema.

Monday, October 17, 2005

Cheesecake project on SourceForge

I registered Cheesecake at SourceForge. People interested in the idea of putting together a "Cheesecake index" that measures the goodness of Python projects are welcome to post in the Open Discussion forum. I got things going there by posting a few ideas contributed by Micah Elliott. If you're interested in participating in the project, send me an email at grig at gheorghiu dot net and I'll add you to the developer list.

Jakob Nielsen on Blog Usability

Usability guru Jakob Nielsen talks about "Weblog Usability: The Top Ten Design Mistakes". I guess I'm guilty of #1 (no author bio), #2 (no author photo) and #10 (generic blog domain name). Oh well, nobody's perfect :-)

Thursday, October 13, 2005

Article on Selenium in October issue of "Better Software"

My "Tool Look: A Look at Selenium" article was published in the Oct. 2005 issue of Better Software. I can now post a PDF version of the article that you can download from here. The "Sticky Notes" are online:

Cheesecake: how tasty is your code?

Update 3/20/06: I'm republishing this post in order to fix this blog's atom.xml index file by getting rid of some malformed XML.

Our friends in the Perl community came up with the concept of KWALITEE: "It looks like quality, it sounds like quality, but it's not quite quality". Kwalitee is an empiric measure of how good a specific body of code is. It defines quality indicators and measures the code along them. It is currently used by the CPANTS Testing Service to evaluate the 'goodness' of CPAN packages. Here are some of the quality indicators that measure kwalitee:
  • extractable: does the package use a known packaging format?
  • has_version: does the package name contain a version number?
  • has_readme: does the package contain a README file?
  • has_buildtool: does the package contain a Makefile?
  • has_tests: does the package contain tests?
I think it would be worth having a similar quality indicator for Python modules. Since the Python CPAN equivalent is the PyPI hosted at the Cheese Shop, it stands to reason that the quality indicator of a PyPI package should be called the Cheesecake index, and I hereby declare that I'm starting the Cheesecake project. The goal of the project is to produce a tool that emits a Cheesecake index for a given Python distribution.

Here are some metrics and tools that I think could be used in computing the Cheesecake index, in addition to some of the CPAN kwalitee metrics:
  • unit test coverage: how many methods/functions are exercised in the unit tests?
  • docstring coverage: how many methods/functions have docstrings?
  • PyFlakes/PyLint validation
As synchronicity would have it, I found a post on comp.lang.py today that refers to well-written Python code. Here are some ideas that Micah Elliott shared about what constitutes a "Pythonic" distribution:

  • Has modules grouped into packages, all are cohesive, loosely coupled, and reasonable length
  • Largely follows PEP conventions
  • Avoids reinventing any wheels by using as many Python-provided modules as possible
  • Well documented for users (manpages or other) and developers (docstrings), yet self-documenting with minimal inline commenting
  • Uses distutils for ease of distribution
  • Contains standard informational files such as: BUGS.txt COPYING.txt FAQ.txt HISTORY.txt README.txt THANKS.txt
  • Contains standard directory structure such as: doc/ tools/ (or scripts/ or bin/) packageX/ packageY/ test/
  • Clean UI, easy to use, probably relying on optparse or getopt
  • Has many unit tests that are trivial to run, and code is structured to facilitate building of tests
  • The first example of a pythonic package that comes to my mind is docutils
Checking for some of these things can be automated. Some properties, such as 'clean UI' or 'reasonable length', are more subjective and harder to automate, but in any case they're all very good ideas and a good starting point for computing the Cheesecake index.

Any other ideas? Anybody interested in participating in such a project? Leave a comment with your email address or send me email at grig at gheorghiu dot net.

Wednesday, October 12, 2005

Software Test and Performance magazine

Came across stpmag.com via a blog post by Alexander Podelko. The neat thing is that you can download all back issues in PDF format. I checked out the October issue and it has some really interesting articles on performance testing, and also on agile software development -- which is very aptly compared to candlestick making (dip a string in wax, get a prototype of a candle, repeat until you get a finished candle while always having a 'working' candle in your hands).

Tuesday, October 11, 2005

Mini HOWTO #3: compiling and installing a custom Linux kernel

  • Go to the /usr/src/linux directory, where linux is a link to the appropriate kernel version (e.g. /usr/src/linux-2.4)
  • Edit the EXTRAVERSION line of /usr/src/linux/Makefile. Change the definition for EXTRAVERSION=versionnumber to something that uniquely identifies your kernel, for example EXTRAVERSION=RHcustom.
  • Run make mrproper to ensure your source files are in a consistent and clean state.
  • Save a copy of the old configuration file /usr/src/linux/.config to a secure location.
  • If you want to reuse an old configuration file as a starting point, copy it to /usr/src/.config and run make oldconfig
  • Customize the kernel. Use make config for a text-based interface, make menuconfig for a curses-based interface or make xconfig for an X-based interface. Select all desired/needed options.
  • Run make dep to set up all dependencies correctly.
  • Run make bzImage to create a gzip-compressed kernel image file. This will compile the kernel, which can be a lengthy process depending on your hardware.
  • Run make modules to build the kernel modules.
  • Copy kernel image file to /boot directory: cp /usr/src/linux/arch/i386/boot/bzImage /boot/vmlinuz-2.4.19-RHcustom
  • Run make modules_install to install the kernel modules into /lib/modules/2.4.19-RHcustom.
  • Build support for an initial RAM disk (initrd) by running: mkinitrd /boot/initrd-2.4.19-RHcustom.img 2.4.19-RHcustom
  • Copy the new kernel’s symbol table and configuration file to the /boot partition: cp /usr/src/linux/System.map /boot/System.map-2.4.19-RHcustom; cp /usr/src/linux/.config /boot/config-2.4.19-RHcustom
  • Update the boot manager configuration files.
    • For GRUB, update /etc/grub.conf with a section for the new kernel.
    • For LILO, update /etc/lilo.conf with a section for the new kernel and then run lilo –v.

Monday, October 10, 2005

Mini HOWTO #2: system monitoring via SNMP

Goal: We want to monitor system resources such as CPU utilization, memory utilization, disk space, processes, system load via SNMP

Solution: Install and configure Net-SNMP

1. Install Net-SNMP
  • if installing from source, the configuration file snmpd.conf will go into /usr/local/share/snmp
  • by default there is no configuration file; it can be generated via the snmpconf Perl utility
2. Configure Net-SNMP by editing /usr/local/share/snmp/snmp.conf

2a. Keep things simple with access control; the following entries can be defined (as opposed to more complicated com2sec, group etc.):

# rwuser: a SNMPv3 read-write user
# arguments: user [noauth|auth|priv] [restriction_oid]
rwuser topsecretv3

# rouser: a SNMPv3 read-only user
# arguments: user [noauth|auth|priv] [restriction_oid]
rouser topsecretv3_ro

# rocommunity: a SNMPv1/SNMPv2c read-only access community name
# arguments: community [default|hostname|network/bits] [oid]
rocommunity topsecret_ro

# rwcommunity: a SNMPv1/SNMPv2c read-write access community name
# arguments: community [default|hostname|network/bits] [oid]
rwcommunity topsecret


2b. Disk space can be monitored by adding entries to the 'disk' section. Example:

disk /
disk /boot
disk /usr

2c. Processes can be monitored by adding entries to the 'proc' section. Example:

proc java
proc postmaster
proc mysqld

2d. System load can be monitored by adding entries to the 'load' section. Example:

load 5 5 5

2e. The EXAMPLE.conf file in the source directory shows more capabilities of the SNMP agent (you can run executables/scripts and return one line of output and an exit code)

3. Start up the SNMP daemon (agent) by running /usr/local/sbin/snmpd. If you want snmpd to start up automatically at boot time, add the line '/usr/local/sbin/snmpd' to /etc/rc.d/rc.local on Red Hat systems, or equivalent on other flavors of Unix

3a. The agent logs to /var/log/snmpd.log (for more detailed debugging info, start the agent with the -D flag)

4. On the SNMP monitoring host, use snmpget to query the SNMP agent running on the target host. The trick here is to know which OIDs to use when you query the agent.

Examples:

Get available disk space for / on the target host:

snmpget -v 1 -c "community" target_name_or_ip .1.3.6.1.4.1.2021.9.1.7.1

(this will return available disk space for the first entry in the 'disk' section of snmpd.conf; replace 1 with n for the nth entry)

Get the number of java processes running on the target host:

snmpget -v 1 -c "community" target_name_or_ip .1.3.6.1.4.1.2021.2.1.5.1

(replace 1 at the end with n for the nth entry in the 'proc' section)

Get the 1-minute system load on the target host:

snmpget -v 1 -c "community" target_name_or_ip .1.3.6.1.4.1.2021.10.1.3.1

Get the 5-minute system load on the target host:

snmpget -v 1 -c "community" target_name_or_ip .1.3.6.1.4.1.2021.10.1.3.2


Get the 15-minute system load on the target host:

snmpget -v 1 -c "community" target_name_or_ip .1.3.6.1.4.1.2021.10.1.3.3


Get various CPU utilization metrics on the target host via snmpwalk:

snmpwalk -v 1 -c "community" target_name_or_ip .1.3.6.1.4.1.2021.11

Sample output:

UCD-SNMP-MIB::ssIndex.0 = INTEGER: 1
UCD-SNMP-MIB::ssErrorName.0 = STRING: systemStats
UCD-SNMP-MIB::ssSwapIn.0 = INTEGER: 0
UCD-SNMP-MIB::ssSwapOut.0 = INTEGER: 0
UCD-SNMP-MIB::ssIOSent.0 = INTEGER: 1
UCD-SNMP-MIB::ssIOReceive.0 = INTEGER: 5
UCD-SNMP-MIB::ssSysInterrupts.0 = INTEGER: 5
UCD-SNMP-MIB::ssSysContext.0 = INTEGER: 8
UCD-SNMP-MIB::ssCpuUser.0 = INTEGER: 0
UCD-SNMP-MIB::ssCpuSystem.0 = INTEGER: 0
UCD-SNMP-MIB::ssCpuIdle.0 = INTEGER: 99
UCD-SNMP-MIB::ssCpuRawUser.0 = Counter32: 1007102
UCD-SNMP-MIB::ssCpuRawNice.0 = Counter32: 3879
UCD-SNMP-MIB::ssCpuRawSystem.0 = Counter32: 544737
UCD-SNMP-MIB::ssCpuRawIdle.0 = Counter32: 238396576


To retrieve a specific metric, for example the number of interrupts, you would do:

snmpget -v 1 -c "community" target_name_or_ip .1.3.6.1.4.1.2021.11.7.0

(we append 7.0 to the OID that we used in snmpwalk, because ssSysInterrupts is the 7th variable in the snmpwalk output)


Get various memory utilization metrics on the target host via snmpwalk:

snmpwalk -v 1 -c "community" target_name_or_ip .1.3.6.1.4.1.2021.4

Sample output:

UCD-SNMP-MIB::memIndex.0 = INTEGER: 0
UCD-SNMP-MIB::memErrorName.0 = STRING: swap
UCD-SNMP-MIB::memTotalSwap.0 = INTEGER: 2048276
UCD-SNMP-MIB::memAvailSwap.0 = INTEGER: 2005604
UCD-SNMP-MIB::memTotalReal.0 = INTEGER: 998560
UCD-SNMP-MIB::memAvailReal.0 = INTEGER: 89896
UCD-SNMP-MIB::memTotalFree.0 = INTEGER: 2095500
UCD-SNMP-MIB::memMinimumSwap.0 = INTEGER: 16000
UCD-SNMP-MIB::memShared.0 = INTEGER: 0
UCD-SNMP-MIB::memBuffer.0 = INTEGER: 234884
UCD-SNMP-MIB::memCached.0 = INTEGER: 459016
UCD-SNMP-MIB::memSwapError.0 = INTEGER: 0
UCD-SNMP-MIB::memSwapErrorMsg.0 = STRING:


To retrieve a specific metric, for example the amount of available swap space, you would do:

snmpget -v 1 -c "community" target_name_or_ip .1.3.6.1.4.1.2021.4.4.0

(we append 4.0 to the OID that we used in snmpwalk, because memAvailSwap is the 4th variable in the snmpwalk output)

Note: for CPU and memory stats, you don't need to add any special directives in the snmpd.conf configuration file

Mini HOWTO #1: chroot-ed FTP with wu-ftpd

Scenario: We have an Apache server whose DocumentRoot directory is /var/www/html. We have wu-ftpd running as the FTP server.

Goal: We want developers to be able to access /var/www/html via ftp, but we want to grant access only to that directory and below.

Solution: Set up a chroot-ed ftp environment

1. Create special 'ftpuser' user and group:

useradd ftpuser

2. Change /etc/passwd entry for user ftpuser to:

ftpuser:x:501:501::/var/www/html/.:/sbin/nologin

(note the dot after the chroot directory)

3. Add /sbin/nologin to /etc/shells.

4. Create and set permissions on the following directories under the chroot directory:

cd /var/www/html
mkdir -p bin dev etc usr/lib
chmod 0555 bin dev etc usr/lib

5. Copy ls and more binaries to the bin subdirectory:

cp /bin/ls bin
cp /bin/more bin
chmod 111 bin/ls bin/more

Also copy to usr/lib all libraries needed by ls and more. Do "ldd /bin/ls" to see the shared libraries you need to copy. For example:

-rwxr-xr-x 1 root root 495474 Jan 7 15:44 ld-linux.so.2
-rwxr-xr-x 1 root root 5797952 Jan 7 15:44 libc.so.6
-rwxr-xr-x 1 root root 11832 Jan 7 15:44 libtermcap.so.2

6. Create special "zero" file in dev subdirectory:

cd dev
mknod -m 666 zero c 1 5
chown root.mem zero

7. Create bare-bones passwd and group files in etc subdirectory:

passwd:

root:x:0:0:root:/root:/sbin/nologin
ftpuser:x:501:501::/var/www/html:/sbin/nologin

group:

root:x:0:root
ftpuser:x:501:

8. Edit /etc/ftpaccess and add following lines:

class all real,guest *

guestgroup ftpuser

chmod no guest,anonymous
umask no guest,anonymous
delete no anonymous
overwrite no anonymous
rename no anonymous

upload /var/www/html / yes root ftpuser 0664 dirs

9. Change group (via chgrp) for files under /var/www/html to ftpuser
  • also change permissions to 775 for directories and 664 for files
  • but be careful to exclude the bin, dev, etc and usr subdirectories

10. Modify httpd.conf so that access to special subdirectories is not allowed:

<Directory /var/www/html/bin>
order deny,allow
deny from all
</Directory>

<Directory /var/www/html/dev>
order deny,allow
deny from all
</Directory>

<Directory /var/www/html/etc>
order deny,allow
deny from all
</Directory>

<Directory /var/www/html/usr>
order deny,allow
deny from all
</Directory>



11. Restart Apache and wu-ftpd


12. Test by ftp-ing as user ftpuser
  • Verify that you can upload/delete files in /var/www/html and subdirectories
  • Verify that you can't access files outside of /var/www/html and subdirectories

System administration and security mini HOWTOs

Over the years I kept notes on how to do various sysadmin/security-related tasks. I thought it might be a good idea to post some of them on this blog, both for my own reference and for other folks who might be interested. The first "Mini HOWTO" post will be on setting up a chroot-ed FTP environment with wu-ftpd.

Friday, October 07, 2005

Configuring OpenLDAP as a replacement for NIS

Here's a step-by-step tutorial on installing OpenLDAP on a Red Hat Linux system and configuring it as a replacement for NIS. In a future blog post I intend to cover the python-ldap package.

Install OpenLDAP
# tar xvfz openldap-stable-20050429.tgz
# cd openldap-2.2.26
# ./configure
# make
# make install

Configure and run the OpenLDAP server process slapd
  • In what follows, the LDAP domain is 'myldap'
  • Change the slapd root password:

[root@myhost openldap]# slappasswd
New password:
Re-enter new password:
{SSHA}dYjrA1-JukrfESe/8b1HdZWfcToVE/cC
  • Edit /usr/local/etc/openldap/slapd.conf

    • Change my-domain to myldap

    • Point 'directory' entry to /usr/local/var/openldap-data/myldap

    • Point 'rootpw' entry to line obtained via slappasswd: 'rootpw {SSHA}dYjrA1-JukrfESe/8b1HdZWfcToVE/cC'

    • Add following lines after 'include /usr/local/etc/openldap/schema/core.schema' line:

include         /usr/local/etc/openldap/schema/cosine.schema
include /usr/local/etc/openldap/schema/inetorgperson.schema
include /usr/local/etc/openldap/schema/misc.schema
include /usr/local/etc/openldap/schema/nis.schema
include /usr/local/etc/openldap/schema/openldap.schema
  • Create data directory:

mkdir /usr/local/var/openldap-data/myldap
  • Start up slapd server:

/usr/local/libexec/slapd
  • Test slapd by running an ldap search:

# ldapsearch -x -b '' -s base '(objectclass=*)' namingContexts
# extended LDIF
#
# LDAPv3
# base <> with scope base
# filter: (objectclass=*)
# requesting: namingContexts
#

#
dn:
namingContexts: dc=myldap,dc=com

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

Populate the LDAP database
  • Create /usr/local/var/openldap-data/myldap/myldap.ldif LDIF file:

dn: dc=myldap,dc=com
objectclass: dcObject
objectclass: organization
o: My LDAP Domain
dc: myldap

dn: cn=Manager,dc=myldap,dc=com
objectclass: organizationalRole
cn: Manager
  • Add LDIF contents to the LDAP database via ldapadd:

# ldapadd -x -D "cn=Manager,dc=myldap,dc=com" -W -f myldap.ldif
Enter LDAP Password:
adding new entry "dc=myldap,dc=com"

adding new entry "cn=Manager,dc=myldap,dc=com"
  • Verify that the entries were added by doing an LDAP search:

[root@myhost myldap]# ldapsearch -x -b 'dc=myldap,dc=com' '(objectclass=*)'
# extended LDIF
#
# LDAPv3
# base with scope sub
# filter: (objectclass=*)
# requesting: ALL
#

# myldap.com
dn: dc=myldap,dc=com
objectClass: dcObject
objectClass: organization
o: My LDAP Domain
dc: myldap

# Manager, myldap.com
dn: cn=Manager,dc=myldap,dc=com
objectClass: organizationalRole
cn: Manager

# search result
search: 2
result: 0 Success

# numResponses: 3
# numEntries: 2
  • Sample LDIF file with organizational unit info: /usr/local/var/openldap-data/myldap/myldap_ou.ldif

dn: ou=Sales,dc=myldap,dc=com
ou: Sales
objectClass: top
objectClass: organizationalUnit
description: Members of Sales

dn: ou=Engineering,dc=myldap,dc=com
ou: Engineering
objectClass: top
objectClass: organizationalUnit
description: Members of Engineering
  • Add contents of LDIF file to LDAP database via ldapadd:

[root@myhost myldap]# ldapadd -x -D "cn=Manager,dc=myldap,dc=com" -W -f myldap_ou.ldif
Enter LDAP Password:
adding new entry "ou=Sales,dc=myldap,dc=com"

adding new entry "ou=Engineering,dc=myldap,dc=com"
  • Sample LDIF file with user info: /usr/local/var/openldap-data/myldap/myldap_user.ldif

dn: cn=Larry Fine,ou=Sales,dc=myldap,dc=com
ou: Sales
o: myldap
cn: Larry Fine
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
mail: LFine@myldap.com
givenname: Larry
sn: Fine
uid: larry
homePostalAddress: 15 Cherry Ln.$Plano TX 78888
postalAddress: 215 Fitzhugh Ave.
l: Dallas
st: TX
postalcode: 75226
telephoneNumber: (800)555-1212
homePhone: 800-555-1313
facsimileTelephoneNumber: 800-555-1414
userPassword: larrysecret
title: Account Executive
destinationindicator: /bios/images/lfine.jpg

dn: cn=Moe Howard,ou=Sales,dc=myldap,dc=com
ou: Sales
o: myldap
cn: Moe Howard
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
mail: MHoward@myldap.com
givenname: Moe
sn: Howard
uid: moe
initials: Bob
homePostalAddress: 16 Cherry Ln.$Plano TX 78888
postalAddress: 216 South Fitzhugh Ave.
l: Dallas
st: TX
postalcode: 75226
pager: 800-555-1319
homePhone: 800-555-1313
telephoneNumber: (800)555-1213
mobile: 800-555-1318
title: Manager of Product Development
facsimileTelephoneNumber: 800-555-3318
manager: cn=Larry Howard,ou=Sales,dc=myldap,dc=com
userPassword: moesecret
destinationindicator: /bios/images/mhoward.jpg

dn: cn=Curley Howard,ou=Engineering,dc=myldap,dc=com
ou: Engineering
o: myldap
cn: Curley Howard
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
mail: CHoward@myldap.com
givenname: Curley
sn: Howard
uid: curley
initials: Joe
homePostalAddress: 14 Cherry Ln.$Plano TX 78888
postalAddress: 2908 Greenville Ave.
l: Dallas
st: TX
postalcode: 75206
pager: 800-555-1319
homePhone: 800-555-1313
telephoneNumber: (800)555-1214
mobile: 800-555-1318
title: Development Engineer
facsimileTelephoneNumber: 800-555-3318
userPassword: curleysecret
destinationindicator: /bios/images/choward.jpg
  • Add contents of LDIF file to LDAP database via ldapadd:

[root@myhost myldap]# ldapadd -x -D "cn=Manager,dc=myldap,dc=com" -W -f myldap_users.ldif
Enter LDAP Password:
adding new entry "cn=Larry Fine,ou=Sales,dc=myldap,dc=com"

adding new entry "cn=Moe Howard,ou=Sales,dc=myldap,dc=com"

adding new entry "cn=Curley Howard,ou=Engineering,dc=myldap,dc=com"
  • Verify entries were added by doing an LDAP search:

[root@myhost myldap]# ldapsearch -x -b 'dc=myldap,dc=com' '(objectclass=*)'
  • Search output should end with:

# search result
search: 2
result: 0 Success

# numResponses: 8
# numEntries: 7

Replace NIS with LDAP

Generate ldif files from /etc/passwd and /etc/group and add them to the LDAP database
  • Generate ldif file for creating 'people' and 'group' organizational units:

  • Edit /usr/local/var/openldap-data/myldap/myldap_people.ldif:

dn: ou=people,dc=myldap,dc=com
objectclass: organizationalUnit
ou: people

dn: ou=group,dc=myldap,dc=com
objectclass: organizationalUnit
ou: group
  • Insert contents of myldap_people.ldif in LDAP database:

# ldapadd -x -D "cn=Manager,dc=myldap,dc=com" -W -f myldap_people.ldif
# tar xvfz MigrationTools.tgz
# cd MigrationTools-46/
  • Edit migrate_common.ph and specify following settings:

$DEFAULT_MAIL_DOMAIN = "myldap.com";
$DEFAULT_BASE = "dc=myldap,dc=com";
$DEFAULT_MAIL_HOST = "mail.myldap.com";
  • Generate passwd.ldif and group.ldif files:

[root@myhost MigrationTools-46]# ./migrate_passwd.pl /etc/passwd /usr/local/var/openldap-data/myldap/myldap_passwd.ldif
[root@myhost MigrationTools-46]# ./migrate_group.pl /etc/group /usr/local/var/openldap-data/myldap/myldap_group.ldif
  • Insert contents of myldap_passwd.ldif and myldap_group.ldif in LDAP database:

# ldapadd -x -D "cn=Manager,dc=myldap,dc=com" -W -f myldap_passwd.ldif
# ldapadd -x -D "cn=Manager,dc=myldap,dc=com" -W -f myldap_group.ldif
Install the pam_ldap and nss_ldap modules
# tar xvfz pam_ldap.tgz
# cd pam_ldap-180
# ./configure
# make
# make install
  • Install nss_ldap.tgz

# tar xvfz nss_ldap.tgz
# cd nss_ldap-243/
# ./configure --enable-rfc2307bis
# make
# make install
  • (See NOTE below before doing this) Edit /etc/ldap.conf (note that there's also /etc/openldap/ldap.conf; you need the one in /etc) and specify the following settings:

base dc=myldap,dc=com
scope sub
timelimit 30
pam_filter objectclass=posixAccount
nss_base_passwd ou=People,dc=myldap,dc=com?one
nss_base_shadow ou=People,dc=myldap,dc=com?one
nss_base_group ou=Group,dc=myldap,dc=com?one
  • (See NOTE below before doing this) Edit /etc/nsswitch.conf and specify:

passwd:     files ldap
shadow: files ldap
group: files ldap

NOTE: Instead of manually modifying /etc/ldap/conf and /etc/nsswitch.conf, you should run the authconfig utility and specify the LDAP server IP and the LDAP base DN ('dc=myldap,dc=com' in our example). authconfig will automatically modify /etc/ldap.conf (minus the nss_base entries), /etc/nsswitch.conf and also /etc/pam.d/system-auth. This is how /etc/pam.d/system-auth looks on a RHEL 4 system after running authconfig:

#%PAM-1.0
# This file is auto-generated.
# User changes will be destroyed the next time authconfig is run.
auth required /lib/security/$ISA/pam_env.so
auth sufficient /lib/security/$ISA/pam_unix.so likeauth nullok
auth sufficient /lib/security/$ISA/pam_ldap.so use_first_pass
auth required /lib/security/$ISA/pam_deny.so

account required /lib/security/$ISA/pam_unix.so broken_shadow
account sufficient /lib/security/$ISA/pam_succeed_if.so uid < default="bad" success="ok" user_unknown="ignore]" retry="3">
Test the LDAP installation with an LDAP-only user

  • Add new user in LDAP database which doesn't exist in /etc/passwd; create /usr/local/var/openldap-data/myldap/myldap_myuser.ldif file:

dn: uid=myuser,ou=People,dc=myldap,dc=com
uid: myuser
cn: myuser
objectClass: account
objectClass: posixAccount
objectClass: top
objectClass: shadowAccount
userPassword: secret
shadowLastChange: 13063
shadowMax: 99999
shadowWarning: 7
loginShell: /bin/bash
uidNumber: 500
gidNumber: 500
homeDirectory: /home/myuser

dn: cn=myuser,ou=Group,dc=myldap,dc=com
objectClass: posixGroup
objectClass: top
cn: myuser
userPassword: {crypt}x
gidNumber: 500
  • Add contents of the myldap_myuser.ldif file to LDAP database via ldapadd:

# ldapadd -x -D "cn=Manager,dc=myldap,dc=com" -W -f myldap_myuser.ldif
  • Create /home/myuser directory and change permissions:

# mkdir /home/myuser
# chown myuser.myuser myuser
  • Change the password for user 'myuser' via ldappasswd:

ldappasswd -x -D "cn=Manager,dc=myldap,dc=com" -W -S "uid=myuser,ou=People,dc=myldap,dc=com"
  • Log in from a remote system via ssh as user myuser; everything should work fine

Adding another host to the myldap LDAP domain
  • On any client machine that you want to join the myldap LDAP domain

    • Make sure the OpenLDAP client package is installed (from source or RPM)

    • Install the nss_ldap and pam_ldap packages

    • Run authconfig and indicate the LDAP server and the LDAP base DN

    • In a terminal console, try to su as user myuser (which doesn't exist locally); it should work

      • To avoid the "home directory not found" message, you'll also need to NFS-mount the home directory of user myuser from the LDAP server

    • Restart sshd and try to ssh from a remote machine as user myuser; it should work (it didn't work in my case until I restarted sshd)


Various notes
  • At this point, you can maintain a central repository of user accounts by adding/deleting/modifying them on the LDAP server machine via various LDAP client utilities such as ldapadd/ldapdelete/ldapmodify
    • For example, to delete user myuser and group myuser, you can run:
# ldapdelete -x -D "cn=Manager,dc=myldap,dc=com" -W 'uid=myuser,ou=People,dc=myldap,dc=com'
# ldapdelete -x -D "cn=Manager,dc=myldap,dc=com" -W 'cn=myuser,ou=Group,dc=myldap,dc=com'
  • I experimented with various ACL entries in slapd.conf in order to allow users to change their own passwords via 'passwd'; however, I was unable to find the proper ACL incantations for doing this (if anybody has a recipe for this, please leave a comment)
  • To properly secure the LDAP communication between clients and the LDAP server, you should enable SSL/TLS (see this HOWTO)
Here are some links I found very useful:

OpenLDAP Quick Start Guide
YoLinux LDAP Tutorial
Linux LDAP Authentication article at linux.com
LDAP for Rocket Scientists -- Open Source guide at zytrax.com
Paranoid Penguin - Authenticate with LDAP part III at linuxjournal.com
Turn your world LDAP-tastic -- blog entry by Ed Dumbill