Setting up secure Directory Services

July 8, 2011

UPDATE: Be sure to read this post regarding the Lion updates.

Where I work we use all Macs along with Open Directory, which is basically Apple’s version of Active Directory. Open Directory handles storing the user accounts and authentication of users of course, but it also handles computer records, centrally managed preferences, etc. As this is something that took me awhile to setup properly I’m going to share both how I went about setting this up in our environment and why I chose to do some things a certain way.

There are two major things I will be covering in this article. The first is how we go about automatically setting up a new computer to join the directory without manually putting in all the information. The second piece is how and why we use login scripts, which are controlled by the directory preferences. I will not be covering how to setup Open Directory itself, how to install the server, how to setup a web server to host the scripts. Nor will I be discussing which preferences you should manage and which you shouldn’t. That is all information you will need to gather for yourself.

Again I will now provide a little background to how we handle the directory system here. First off, I do not use client-binding for joining the directory. Is it more secure? probably. Is it a pain in the butt? definitely. When we buy a machine it does not get put on a single desk belonging to a single person for the next 6 years until we throw it away. We buy a new machine and put it on the desk of the person who, at this point in time, has the greatest need for the most powerful computer. Their old machine then goes down to the person who has the greatest need for a computer of the speed of that hand-me-down. And so on until we at-least pull out a 6 year old computer and throw it away. For every computer we purchase we might shuffle down 3 or 4 computers. This does create a lot of extra work for us (not too much because of the information in this post) but it makes our staff much more productive. There is no reason to put a brand new iMac on the desk of the guy who uses his computer for 3 hours a week and at that, is almost always only in Word so he can print something.

Doing this computer shuffle causes great headaches for client-binding, so we disabled it completely. Here is the thing. I don’t care if people browse our directory from on campus, because that means they are authorized to be here (in one way or another) and there is nothing that critical in our directory. What I care about is that the client computer knows it’s talking to the right server and not a fake. That is where Apple wonderful implementation of SSL for Open Directory comes in. Out of the box, a client machine CAN NOT connect to ANY OD (Open Directory) server over SSL. It will fail every time. The reason for this is that the LDAP client uses a dedicated directory of authorized SSL certificates, which on a clean install is empty.

Therefor each new computer that is setup is told to connect to OD via SSL, and has the SSL certificate installed. This sets the reported security level of the OD connection to Encrypted, which to me means authorized as well because it means it is connecting with an SSL certificate that I manually installed. This means I have no fear of running login scripts that are provided by the Open Directory database, because they are also secure in that they are the ones I specifically put in place. Now, onto the fun stuff.

Part 1 – Joining a workstation to the directory

Setting up the server

What? I said I wasn’t going to cover how to setup Open Director or the server. Well I’m not. I am going to cover the single server configuration item that needs to be setup correctly for the method I choose to follow.

First up, in Server Admin under the server itself you can setup the SSL certificates that are available to the various services on the server. You want to setup a self-signed certificate with the name of the LDAP/Open Directory server you plan to have all your clients connect to. I chose to use ldap.hdcnet.org.  This should be a name you don’t plan to use for anything else, and you do NOT want to just use your standard certificate that you use for web on the server or whatever else, this must be a dedicated certificate. Also, make it expire far in the future. When I set this up I set it for 10 years, so it will expire in 2019. Which means in 8 years I will have to deploy a new certificate to all computers (no sweat, that is what login scripts or managed software deployment is for).

So now that we have our 10+ year certificate and have it all self-signed, we need to go over to the Open Directory service. You should already be setup and running as a Master role. Under the LDAP tab you want to Enable SSL and select your self-signed certificate. Then go to the Policies tab. I have every single check-box CLEAR and not checked. You can turn on Encrypt all Packets if you want, I don’t because I have other services that I want to be able to use just standard LDAP and not LDAP over SSL (some because they don’t support LDAPS). For sure you do not want the Enable authenticated directory binding option selected, that disables client-side binding to the directory. I also keep Kerberos disabled as I had trouble getting it setup, your mileage may vary.

Save your settings and continue on to the next step.

Preparing Client SSL

Next you need to prepare the SSL certificate to be used by the client. This part needs to be done in Terminal, so get ready to see some black on white. First off, create a directory somewhere to stage all this stuff. For these purposes we’ll assume that is ~/SSL. Now create a ~/SSL/certs directory and copy your certificate from the server there. You can find the certificate on your server in the /etc/certificates folder (again you will need Terminal or something, it’s a hidden folder). In our case, the file is called ldap.hdcnet.org.crt and that file gets copied onto your local machine in the ~/SSL/certs folder.

Once that is done, you want to open up Terminal and go to that folder (cd ~/SSL/certs) and create a hash symbolic link to the file, since openssl needs the real file and the link to it.

$ cd ~/SSL/certs
$ openssl x509 -hash -noout -in ldap.hdcnet.org.crt
b7bf7f59
$ ln -s ldap.hdcnet.org.crt b7bf7f59.0

The third line is the result of the openssl command. It is the hash of the certificate. The last line creates a symbolic link to the ldap.hdcnet.org.crt file using the name b7bf7f59.0, yes you need to append the “.0” (dot zero) to the hash returned by the openssl command. Next you need to create a ldap.conf file that will connect you to your LDAP server. Create the file ~/SSL/ldap.conf with the contents:

TLS_REQCERT      demand
TLS_CACERTDIR /etc/openldap/certs

If you want to customize the file further, you can, but that is all you need and is in fact all that is in my ldap.conf. With these files you can now test on a single client machine.

Testing a Client

I highly recommend you test your configuration manually on a client before moving on to the automation process. It’s really fast and simple. In short, you want to take those 2 files from above (the symbolic link and the certificate file itself) and put them on the test machine under the /etc/openldap/certs folder. Doing so is probably easiest using Terminal.  You will need to use the sudo command to gain access to that directory as it is owned by root. The /etc/openldap directory should exist, create the certs folder and drop the two files in place there.

Now back in the GUI go to either Directory Utility (if pre-Snow Leopard) or System Preferences, Accounts and then open Directory Utility from there. You will want to enter the name of the server the same as you entered when you created the SSL certificate. It should connect.  Once it is connected verify that the SSL checkbox is turned on.  Once it is connected and shows a green dot, reboot the client.  This shouldn’t really be needed but I have run into quirks where it shows that everything is fine but after a reboot it won’t connect again.  Once the client is rebooted open up Terminal again and run the dscl command.

$ dscl
> cd LDAPv3
> cd ldap.hdcnet.org
> read . TrustInformation
TrustInformation: Anonymous Encryption

The information in the TrustInformation key is what we are interested in. Anonymous means it is not connected by name/password (i.e. no client binding). Encryption means that the SSL connection is working. That is the key part, if it is encrypted that means our custom SSL certificate is working. It will NOT show Encryption if it connects to another server that has an SSL certificate since that certificate has not been installed locally by the root user. This gives us a pretty good measure of security. If your TrustInformation has Encryption, you are set to move on.

Automated Install

Now that we have tested the files and know they work correctly we can create a .pkg file that automatically installs the files and joins the directory all in one step. I called my package LDAPSSL, and setting it up is extremely simple. Create a package that drops the certs folder and the ldap.conf file into the /private/etc/openldap directory, make sure they are marked as owned by root and not writable by group or other. Next create a script file called postinstall.sh (you may need to do this in Terminal, I don’t know if it can be done in TextEdit as that will try to add the .txt extension). The postinstall.sh script will be set to be the postflight script for the package and it’s contents will be:

#!/bin/sh
#

sudo defaults write com.apple.loginwindow EnableMCXLoginScripts -bool true
sudo defaults write com.apple.loginwindow MCXScriptTrust Encryption

EXISTS=`dscl localhost list /LDAPv3 | grep ldap.hdcnet.org`
if [ -z "$EXISTS" ]; then
sudo dsconfigldap -a ldap.hdcnet.org -x
sudo dscl /Search -create / SearchPolicy CSPSearchPath
sudo dscl /Search -append / CSPSearchPath /LDAPv3/ldap.hdcnet.org
fi

sudo dseditgroup -o edit -a hdc_admin -t group -n /Local/Default admin
sudo dseditgroup -o edit -a hdc_admin -t group -n /Local/Default lpadmin

Here is what this script does. The first two lines enable the execution of login scripts and sets the minimum trust level to Encryption. This means that even if somebody manages to get your client to connect to another directory server, unless you install the SSL certificate as root then it will not run the login scripts. This isn’t 100% secure, but if somebody can install stuff as root then that means they have access to do whatever they want on the computer anyway. That makes it good enough for me.

The second block checks to see if the given server name is already setup on the client. If it isn’t listed, then it adds the server via the dsconfigldap command using the server name ldap.hdcnet.org, the -x parameter tells it to use SSL for the connection.

The last block is not necessary, but very helpful. I have defined a group in the directory called hdc_admin which contains all the I.T. admin users (3 of us). These last 2 lines add the hdc_admin group to the local admin and lpadmin groups. This will allow anybody who is a member of the hdc_admin group to be an administrator on the client machine, without needing a local account. The admin group lets them be a normal Administrator. The lpadmin group lets them administer the printing system. I don’t know why these two are separate, but it does let you give somebody admin access to printers but not the rest of the computer.

In reality, we also keep a local admin user (called Network Administrator) on each computer so if something ever goes foul with the directory we can still login to admin the client machines, but this method lets us keep that password highly secret and still give part time help access to admin the machines without giving them the password we use for most everything else.

Once the package has been built, test it on a new machine. Installing the LDAPSSL.pkg package should now install the files and configure the machine to connect to the directory. I would again use the dscl command to verify the computer after it has rebooted. If all is well then you can move to the final step, which is using login scripts to do special configuration on the client (such as the printer configuration script mentioned in my previous post).

Part 2 – Login/Logout Scripts

Assuming you got this far, the hard part is over. Setting up the script is fairly strait forward. You will want to create two .sh script files. One will be for login scripts, the other for logout scripts.  In reality, it has been 2 years and I have never used the logout scripts so you can really skip that if you want. Once you have created the two scripts you will put them in Workgroup Manager as the login/logout scripts. I have a Computer Group specifically for the scripts, but you can set that up however you want. The content of the scripts will be the following:

#!/bin/sh
#
# Script to automatically download and run scripts from a central server.
#

SCRIPTS="login.scripts"
BASEURL="http://WEBSERVER/scripts"
USERHOME=`finger -mp $1 | grep "^Directory: " | sed 's/^Directory\: \([\/A-Za-z0-9\. ]*[A-Za-z0-9]\)*.*$/\1/'`
LOCALSCRIPTS="$USERHOME/.scripts"
SCRIPTUSER="$1"
VERBOSE=0

#
# Create the local script storage in the users home folder.
#
mkdir -p "$LOCALSCRIPTS"

#
# Get the list of scripts to download and run.
#
if [ $VERBOSE -eq 1 ]; then echo "Downloading $SCRIPTS..."; fi
curl -s -f -o "$LOCALSCRIPTS/$SCRIPTS" "$BASEURL/$SCRIPTS"
if [ $? -ne 0 ]; then DOWNLOAD=0; else DOWNLOAD=1; fi

#
# Read the list of scripts and download and run each one.
#
SCRIPTLIST=`cat "$LOCALSCRIPTS/$SCRIPTS" 2>/dev/null`
for script in $SCRIPTLIST; do
if [ $VERBOSE -eq 1 ]; then echo "Processing script $script..."; fi

if [ $DOWNLOAD -eq 1 ]; then
curl -s -f -o "$LOCALSCRIPTS/$script" "$BASEURL/$script"
fi

if [ -e "$LOCALSCRIPTS/$script" ]; then
if [ $VERBOSE -eq 1 ]; then echo "\tRunning..."; fi
chmod a+x "$LOCALSCRIPTS/$script"
"$LOCALSCRIPTS/$script" "$SCRIPTUSER" "$LOCALSCRIPTS" "$BASEURL"
fi

if [ $VERBOSE -eq 1 ]; then echo "\tFinished."; fi
done

if [ $VERBOSE -eq 1 ]; then echo "Finished processing all scripts."; fi

The script above is for the login scripts.  If you want to make it work for logout scripts simply change the line at the top that says SCRIPTS=”login.scripts” to SCRIPTS=”logout.scripts”. You will also need to change the line just below that to a valid webserver link. Now here is what this script does and how to use it.

All your scripts get put on your web server. For example, I use http://kempis.hdcnet.org/scripts.  Scripts is a folder that contains at a minimum 2 files. login.scripts and logout.scripts. These two files contain a list of script file names to be downloaded and run. One script per line. If the first line of the login.scripts file is printers.sh then the script will download http://kempis.hdcnet.org/scripts/printers.sh and execute it. It will also save all the downloaded scripts into the users ~/.scripts folder so that if they are off-line (for example a laptop that is not plugged into the internet) it will still have the script available to run.

You can put each individual script as a login script in Workgroup Manager, but I prefer this method. There is only once script in MCX and I don’t have to update MCX again when I add a script. The script can be anything. I have used Bash shell scripts, Perl scripts and even once a PHP script. I currently have scripts running at login that do the following:

  • One-time updates to the users aut0-launch applications (add an app once, but if the user removes it don’t add it back in)
  • ARD permissions fixes (Allow directory admin users to remote desktop into the machine)
  • Set the Software Update Server URL to the appropriate setting. Apple’s recommended way to do this is to manually keep track of which computers are running which versions of OS X and put them in appropriate MCX groups each with the correct URL. This script checks the Mac OS X version and sets the CatalogURL correctly so I do not need to keep track of the computers’ versions.
  • Printer Configuration. In my previous blog post I talked about how to configure printers with MCX, this is the script that runs to set all the default printer settings at login.