Monday, September 21, 2015

FreeIPA with Kerberos for OSX 10.7+

Wow, it's been a long time since I last posted something! I finally completed a project I've been working on for some time now and wanted to share it so that others could benefit. This is article is going to be pretty dense and technical. A lot of the documentation you'll see here came directly from http://linsec.ca/Using_FreeIPA_for_User_Authentication#Mac_OS_X_10.7.2F10.8. That page has some other information regarding FreeIPA so I would encourage you to check it out. This post expands a good bit on the original document to include, among other things, a more security focused approach to connecting to the server including the use of LDAPS and disallowing "week crypto" in the Kerberos configuration.

Kerberos and Pam Configuration

Create a file in /Library/Preferences/edu.mit.Kerberos with the following contents:
[domain_realm]

   .example.com = EXAMPLE.COM

   example.com = EXAMPLE.COM

[libdefaults]
   default_realm = EXAMPLE.COM
   allow_weak_crypto = no
   dns_lookup_realm = true
   dns_lookup_kdc = true
   rdns = false
   ticket_lifetime = 24h
   forwardable = yes
   renewable = true

[realms]
   EXAMPLE.COM = {
       kdc = ipa.example.com:88
       master_kdc = ipa.example.com:88
       admin_server = ipa.example.com:749
       default_domain = example.com
       pkinit_anchors = FILE:/etc/ipa/ca.crt
   }
Download the ca.crt file, create a directory as shown below and place the ca.crt within that directory:
# sudo -i
# cd /etc/
# mkdir ipa
# cd ipa
# curl -OL https://ipa.example.com/ipa/config/ca.crt
Edit /etc/pam.d/authorization to match the following:
 # authorization: auth account
 auth       optional       pam_krb5.so use_first_pass use_kcminit default_principal
 auth       sufficient     pam_krb5.so use_first_pass default_principal
 auth       optional       pam_ntlm.so use_first_pass
 auth       required       pam_opendirectory.so use_first_pass nullok
 account    required       pam_opendirectory.so
Edit /etc/pam.d/screensaver to match the following:
 # screensaver: auth account
 auth       optional       pam_krb5.so use_first_pass use_kcminit default_principal
 auth       required       pam_opendirectory.so use_first_pass nullok
 account    required       pam_opendirectory.so
 account    sufficient     pam_self.so
 account    required       pam_group.so no_warn group=admin,wheel fail_safe
 account    required       pam_group.so no_warn deny group=admin,wheel ruser fail_safe
Edit /etc/pam.d/sudo to match the following:
 # sudo: auth account password session
 auth       sufficient     pam_krb5.so try_first_pass default_principal
 auth       required       pam_opendirectory.so use_first_pass
 account    required       pam_permit.so
 password   required       pam_deny.so
 session    required       pam_permit.so

IPA Enrollment

Because we cannot enroll the system into IPA the easy way, we need to visit the web UI and add a new host. In the IPA web UI, go the Identity and then the Hosts page. Click the "Add" button, where you will need to add the fully qualified domain name of the host (e.g. mac.example.com ), and then click the "Add and Edit" button. You don't need to add much here, other than the MAC address of the system, and the SSH public keys, which can be found in /etc/ssh_host_dsa_key.pub and /etc/ssh_host_rsa_key.pub. The Ethernet MAC address can be found via either ifconfig or System Preferences.
Generate ssh_host_rsa_key

If you don't have /etc/ssh_host_rsa_key.pub or /etc/ssh_host_dsa_key.pub then follow the steps below to create one:
# cd /etc
# ssh-keygen -t rsa -b 4096 -f /etc/ssh_host_rsa_key

Generate keytab File

This, unfortunately, does not generate a keytab file for the host, so on the server, using the ipa-getkeytab program, we will create an obtain the keytab for our new host:

    # ipa-getkeytab -s ipa.example.com -p host/your_hostname.example.com -k ~/mac.keytab

Now that the keytab is generated, scp it from the server to the new workstation and place it in /etc/krb5.keytab. Make sure the file is owned by the user root and group wheel (root:wheel) and is mode 0600.
OpenSSL and OpenLDAP Configuration

Import and symlink the CA certificate to /System/Library/OpenSSL/certs/:
# sudo -ipa
# cd /System/Library/OpenSSL/certs
# curl -OL https://ipa.example.com/ipa/config/ca.crt
# ln -s /System/Library/OpenSSL/certs/ca.crt $(openssl x509 -noout -hash -in /System/Library/OpenSSL/certs/ca.crt).0
Now we need to import the ca.crt file to Keychain Access and trust it.

First, either copy the ca.crt file from /System/Library/OpenSSL/certs to a more convenient location (e.g. Desktop) or re-download it from https://ipa.example.com/ipa/config/ca.crt using your web browser. Next, open up Keychain Access. Select "System" under the "Keychains" menu on the left of the screen. Next, select the "Certificates" from beneath the "Category" field. Click on the padlock to unlock the keychain. Click on the "+" symbol at the bottom of the window, navigate to where you saved the ca.crt file, highlight it and click "Open". You'll now see a new entry in the list with a red dot. This red dot indicates that the certificate is untrusted. We need to trust the CA by double clicking on the entry, click the arrow next to "Trust" and in the drop down next to "When using this certificate:" select "Always Trust. Close the window and Keychain Access window to save your changes.

Finally, we need to configure the ldap.conf located in /etc/openldap/ldap.conf. Edit the file to look like the following example:
#
# LDAP Defaults
#
# See ldap.conf(5) for details
# This file should be world readable but not world writable.
#BASE    dc=example,dc=com
#URI    ldap://ldap.example.com ldap://ldap-master.example.com:666
#SIZELIMIT    12
#TIMELIMIT    15

DEREF        never
REFERRALS    off

TLS_REQCERT    demand
TLS_CACERTDIR    /System/Library/OpenSSL/certs
Once all the above changes have been made you need to reboot to reload all of the services that read these files. If you do not, the following steps will fail.
Directory Utility Setup

To begin, launch Directory Utility. On the Services pane will be three service names: Active Directory, LDAPv3, and NIS. After authenticating (click the padlock), click the "LDAPv3" line to highlight it, then click the little pencil icon to edit.

Click the "New..." button and enter the IPA server name (ipa.example.com) in the "Server Name or IP Address" field. Make sure that "Encrypt using SSL" is checked, and "Use for contacts" is unchecked (you could, optionally, use the LDAP directory for contact information but the point of this particular exercise is for authentication, so at this point turn it off).

You should be back at the option list. Enter "IPA LDAP" for the configuration name, and select "Custom" for the LDAP Mappings. Make sure the SSL checkbox is checked. Now highlight the new entry and click the "Edit..." button

Under the "Connection" tab, change a few of the defaults:
  • Open/close times out in 10 seconds
  • Query times out in 10 seconds
  • Connection idles out in 1 minutes
Unfortunately, at least in OS X 10.8, you cannot change the re-bind attempts timeout from the default of 120 seconds; you can change it in OS X 10.7, so if using that version set it to 10s as well. Also ensure that SSL encryption and the custom port are checked and that the custom port is 636.

User Mapping

Under the "Search & Mappings" tab, you will need to add a few record types and attributes. In the first pane, click the "Add..." button and add the Record Type of "Users". This should show up in the first pane, so in the second pane click the "Add..." button and you'll have an entry field. Type in inetOrgPerson here and press enter or click outside of the edit box.

Now you should be able to define the Search base near the bottom of the window; set it to dc=example,dc=com and make sure the "all subtrees" radio button is selected.

Click on "Users" in the first pane and then click the "Add..." button. We will be adding a number of Attribute Types and setting the associated map, similar to how we mapped "Users" to "inetOrgPerson". The Attribute Types and their respective values are noted below:
AuthenticationAuthority: uid
HomeDirectory: #/Users/$uid$
NFSHomeDirectory: #/Users/$uid$ (NOTE: odd as it sounds, this seems to be required, even if you're not using NFS)
PrimaryGroupID: gidNumber
RealName: cn
RecordName: uid
UniqueID: uidNumber
UserShell: loginShell
For the above you, you have a choice for the HomeDirectory and NFSHomeDirectory options. If you use homeDirectory for both, it will map to /home/[user] which is fine for automounted home directories (/home/ is an automount on OS X). However, if you want a local directory on the machine for the user (not an automounted/shared home directory), use #/Users/$uid$ instead.

Once this is done, click the "OK" button to save and return to the server list. Click "OK" again and head to the Search Policy pane. In the "Authentication" page, you should see "/LDAPv3/ipa.example.com" beneath "/LocalDefault". If you do not, click the "+" button and add your new LDAP server definition (e.g. "/LDAPv3/ipa.example.com"). It will show up after the "/Local/Default" domain. Make sure that the "Search" field is set to "Custom path".

Now move to the Directory Editor pane. If everything is setup correctly, you should see a list of users pulled from the IPA server's LDAP directory on the right (if you don't, you likely missed something during the SSL configuration. You should revisit the previous section on OpenSSL and OpenLDAP configuration). If you click one of the user names, you should see a pane full of name and value pairs, which is what OS X is mapping locally from the directory server. The items in grey are the static bits that OS X generates, and the names starting with "dsAttrTypeNative:" are the un-mapped bits from the LDAP directory. You should see quite a few of them, including kerberos principal name, password policy references, the "dsAttrTypeNative:ipaUniqueID", and so on. More importantly, you should see at the top various bits that are being mapped properly.

To see the results of your changes without rebooting, go to the Terminal and use dscacheutil to empty the cache which will allow it to pick up the changes:
$ dscacheutil -flushcache
Next, use dscacheutil to do a lookup to make sure that the user is actually found:

 $ dscacheutil -q user -a name jsmith
 name: jsmith
 password: ********
 uid: 1000
 gid: 100
 dir: /Users/jsmith
 shell: /bin/bash
 gecos: Smith, John

Group Mapping

Now that the user information is present, the last step is to setup the groups (from the above, you can see that the group names are missing). Once again, in Directory Utility you want to go to the "Search & Mappings" pane and this time add a "Groups" record type, which should map to "posixgroup". There are only a few attributes to add under the Groups record type:
PrimaryGroupID: gidNumber
RecordName: cn
As with the Users record type, you will need to set the search base for groups. Click the "Groups" record type and use cn=groups,cn=accounts,dc=example,dc=com for the search base.

Click "OK" to save. You can now go to the "Directory Editor" and select "Groups" from the "Viewing" pulldown menu and you should see all of the groups from your directory, just as you did for the users. Should also see a lot of information in the Name/Value screen showing that the groups were properly found.

Once again, head back to the Terminal, flush the cache, and do a lookup:
 $ dscacheutil -flushcache
 $ dscacheutil -q group -a name jsmith
 name: jsmith
 password: *
 gid: 1000
 $ id jsmith
 uid=1000(jsmith) gid=100(_lpoperator) groups=100(_lpoperator)
You'll notice a lot of weird looking groups in the previous `id` command. This is because Apple has chosen to use a non-standard number schema for it's /etc/groups file. Sometime in the future we might be able to find a way to clean this up, but for now, your user account will just look something like the above if you are a member of any ldap groups that have a gidNumber that overlaps one of the local groups.

Creating Home Directories

If you elected to have the home directory on the local system (using /Users/[user]), you have one further step to make. OS X does not auto-create home directories for LDAP-based users, so you will need to create them yourself. All you need to do is create the directory, upon first login, the rest will be populated:
$ sudo -i
# mkdir /Users/jsmith
# chown jsmith:100 /Users/jsmith

 System Preferences: Login

Finally, make a trip to System Preferences, in particular the Users & Groups settings. Click the "Login Options". Here you will want to ensure that the following are set:
  • Display login window as: Name and password (otherwise network users cannot login)
  • Allow network users to log in at login window (checked, you can restrict to certain users by clicking "Options..."
  • Network Account Server is set and has a green light (should display the IPA server's hostname)

Enable Mobile Accounts for LDAP Users

In order to be able to log in to your system while disconnected from the local network, you will need to turn on "Mobile Accounts" for your user (or the user using the system if you're setting this up for someone else). This is most easily done from the command line using the following command:
$ sudo /System/Library/CoreServices/ManagedClient.app/Contents/Resources/createmobileaccount -n jsmith
If setting this up for yourself or the user you are setting it up for is present, it may also be beneficial to present the password to the previous command with the -P flag. This will prompt you to enter the user's password. If this fails, mobile accounts will pick up the password from the next time the user logs in while connected to the network

To add the password run:
sudo /System/Library/CoreServices/ManagedClient.app/Contents/Resources/createmobileaccount -n jsmith -P
Sometimes the above does not work using the users actual username. For some, yet to be determined, reason you may need to use the users real name in place of the username in the format of "Lastname, Firstname" (including the double quotes). More work needs to be done to determine why this is and if it's actually required or not. If the above commands do not work as advertised, try running them with "Lastname, Firstname" in place of "username".