Mikko Kortelainen

Ubuntu 12.04 Active Directory Authentication

Update 2015-06-16:Ubuntu 14.04 Active Directory Authentication

Authenticating Linux users against Active Directory has traditionally been hard. There's a multitude of HOWTOs on how to do it, and every one of them seems to do it a bit differently. This is because environments and goals vary, and there are many ways to achieve a particular goal. I will add my version to the mix. This one fetches users and groups from Active Directory LDAP using a machine account added using the Samba tools, and authenticates users to the Active Directory Key Distribution Center using Kerberos.

I will use the LDAP schema that comes with the Microsoft Services for Unix Tools which has later been renamed to Subsystem for UNIX-based Applications (SUA).

I will configure the server to fetch user account and group data from Active Directory LDAP using the nss_ldap Name Service Switch module. This information will be used in addition to the data found in local /etc/passwd and /etc/group files. I will also configure the Kerberos authentication client using MIT Kerberos libraries and tools, and configure the pam_ldap Pluggable Authentication Module to utilize it. The Samba tools are used to add a machine account for the host, as well as a keytab file which will be used for querying Active Directory LDAP. No need to add special user accounts for querying AD.

Still, the whole process is not simple by any means. Here's an outline:

  1. Install and configure the MIT Kerberos client and test that it works
  2. Install and configure the Samba tools
  3. Use the Samba tools to "join" the machine to the domain, or in other words, to create a machine account for it in the AD LDAP
  4. Add forward and reverse DNS records for the machine
  5. Create the host keytab file
  6. Map a suitable Kerberos principal to the new machine account in AD, and test that it works with the keytab file
  7. Make sure there's a valid ticket for the machine account all the time
  8. Extend AD LDAP schema using the SFU/SUA tools
  9. Install LDAP tools and test connecting to the AD and see the internal structure
  10. Install and configure nss_ldap to fetch user and group information from AD
  11. Install and configure pam_ldap to authenticate to AD
  12. Add a PAM directive to create a home directory for a user logging in the first time if such a directory does not yet exist

1   Install and configure kerberos

Install the Kerberos library, related PAM library, and also Samba client which we will just use to create a machine account in the domain, and create a keytab file.

sudo aptitude install libpam-krb5 krb5-user smbclient

Type in your AD Kerberos realm when prompted. It should generally be your domain name in capital letters ("koo.fi" becomes "KOO.FI"). If your DNS is working properly, that should be all that is needed for Kerberos client to work in a basic manner. Otherwise you may need to add your servers to /etc/krb5.conf.

Try getting a ticket:

# kinit Administrator@KOO.FI
Password for Administrator@KOO.FI:
# klist

Ticket cache: FILE:/tmp/krb5cc_0
Default principal: Administrator@KOO.FI

Valid starting     Expires            Service principal
06/09/11 12:01:22  06/09/11 22:01:06  krbtgt/KOO.FI@KOO.FI
    renew until 06/10/11 12:01:22

That shows we now have a valid ticket and the Kerberos authentication is working fine to the domain controller.

Some extra configuration at this point in krb5.conf. The appdefaults/pam configuration section is where pam_krb5 module gets its configuration from (see for example http://linux.die.net/man/5/pam_krb5).

[appdefaults]
pam = {
        realm = KOO.FI
        ticket_lifetime = 1d
        renew_lifetime = 1d
        forwardable = true
        proxiable = false
        retain_after_close = false
        minimum_uid = 2
        try_first_pass = true
        ignore_root = true
}
[libdefaults]
        default_realm = KOO.FI
        default_keytab_name = FILE:/etc/krb5.keytab
        default_tkt_enctypes = rc4-hmac des-cbc-crc des-cbc-md5
        default_tgs_enctypes = rc4-hmac des-cbc-crc des-cbc-md5

[realms]
        KOO.FI = {
                auth_to_local = DEFAULT
        }
[domain_realm]
        .koo.fi = KOO.FI
        koo.fi = KOO.FI

The auth_to_local rule will try to parse the local username from the Kerberos principal name, otherwise it will use the principal name itself (see http://linux.die.net/man/5/krb5.conf).

1.1   Configure Samba

We only need the Samba client because it is a convenient tool to create a machine account in Active Directory. Configure Samba client using /etc/samba/smb.conf:

[global]
  netbios name = UBUNTU
  realm = KOO.FI
  workgroup = KOO
  security = ADS
  kerberos method = system keytab

The "system keytab" kerberos method states that the Kerberos system is used to find the keytab file (see http://www.samba.org/samba/docs/man/manpages-3/smb.conf.5.html#KERBEROSMETHOD). We defined it in krb5.conf above.

1.2   Join to Domain

Join the machine to the domain. This command creates a machine account in AD:

# sudo net ads join -U Administrator
Enter Administrator's password:
Using short domain name -- KOO
Joined 'UBUNTU' to realm 'koo.fi'
DNS update failed!

If you get the "DNS update failed" error message, you just need to create a host record in DNS manually (both forward and reverse).

After this, you should basicly be able to remove smbclient should you wish to do so. I didn't bother.

1.3   Create a Keytab file

A keytab file should automatically be created by net ads join as /etc/krb5.keytab. This was stated earlier in krb5.conf. You can also create the keytab file manually with the following command (from the smbclient package):

# sudo net ads keytab create

Test the keytab with this command:

# sudo kinit -V -k 'UBUNTU$'
Authenticated to Kerberos v5

That means you are able to authenticate with the keytab file using principal UBUNTU$. If you have error messages, you may turn on debug output in krb5.conf:

[logging]
    default = SYSLOG:DEBUG

The next step is to go to your Active Directory domain controller and use the ktpass.exe tool to create a host principal ("host/ubuntu.koo.fi") and map it to the machine account that was created ("UBUNTU$").

C:\Users\Administrator.KOO>ktpass /princ "host/ubuntu.koo.fi@KOO.FI" /mapuser UBUNTU$@KOO.FI
Targeting domain controller: DC1.koo.fi
Using legacy password setting method
Successfully mapped host/ubuntu.koo.fi to ubuntu$.

Now you should be able to run:

# sudo kinit -V -k 'host/ubuntu.koo.fi@KOO.FI'
Using default cache: /tmp/krb5cc_0
Using principal: host/ubuntu.koo.fi@KOO.FI
Authenticated to Kerberos v5

1.4   Make Sure There's a Valid Machine Account Ticket all the time

Now that we have a working keytab file, we'll have to make sure we have a valid ticket all the time, and that it can be accessed by every user who needs it. Active Directory has a maximum ticket lifetime of 10 hours. The keytab file by itself will not be enough to authenticate, but it can be used to get a ticket from a KDC. We can do it with a cron job ("sudo crontab -e"):

0 * * * * kinit -k ‘host/ubuntu.koo.fi@KOO.FI’ -c /tmp/krb5cc_host; chmod +r /tmp/krb5cc_host

That will run kinit every hour as root, and save the host credentials in the Kerberos credentials cache file /etc/krb5cc_host. This file contains the live tickets, and needs to be refreshed periodically. It also needs to be readable by all users who need to read information from the LDAP directory. That is why we add world-readable permissions to it.

Security-wise, it is important to remember, that anyone who has read permissions to the credentials cache can pose as the principal whose keys are in the file. In our case, it is the AD machine account. You may wish to restrict who has access to the file using groups or acls. You could revoke access from nobody:nogroup like this:

0 * * * * kinit -k ‘host/ubuntu.koo.fi@KOO.FI’ -c /tmp/krb5cc_host; chmod +r /tmp/krb5cc_host; setfacl -m u:nobody:--- /tmp/krb5cc_host; setfacl -m g:nogroup:--- /tmp/krb5cc_host

All that has to be on one line in the crontab. Or you could create a shell script for it. You also need to install the "acl" package to get the setfacl command.

To see what credentials are currently in the file, run:

# klist -c /tmp/krb5cc_host

2   Install OpenLDAP tools

While not strictly necessary, it is helpful at this point to test authentication to AD and fetching stuff from it, usind ldap-utils and SASL module:

# sudo aptitude install ldap-utils libsasl2-modules-gssapi-mit

Edit /etc/ldap/ldap.conf (the OpenLDAP tools configuration file) and put your own details there:

BASE   dc=koo,dc=fi
URI ldap://dc1.koo.fi ldap://dc2.koo.fi

Now, after fetching a ticket with the command "kinit username@REALM" you should be able to run plain "ldapsearch" and see the contents of your whole Active Directory (or up to about a thousand entries) using that Kerberos ticket. This will turn out handy in the next section.

You should also be able to authenticate using the principal in your keytab file at this point. Destroy your ticket by running kdestroy. After fetching a ticket with the keytab file by running kinit -V -k 'host/ubuntu.koo.fi@KOO.FI', you should also be able to ldapsearch without problems.

3   The Active Directory Schema

The Active Directory LDAP Schema must be extended by installing the Microsoft Services For Unix on an Active Directory Domain controller. The schema extension adds some extra attributes to the directory that allow you to specify user's ID number, home directory, shell etc. You can edit them in a new tab which should appear in the Active Directory Users and Computers snap-in. It looks something like this:

The attribute names and objectclasses used in the following discussion have been taken from my setup. They may vary depending on which MS SFU version you have installed at the moment, or which version you installed the first time. It is therefore advisable to take a look at your own directory and take the correct attribute names from there. To do that, go to AD Users and Computers, add UNIX attributes for one user with UNIX attributes set up, and also one such group, and then run ldapsearch to see the attributes:

# ldapsearch "(cn=Mikko K. Kortelainen)"

That whill search by the username and should return a list of attributes saved for the user. My attributes look like this (I removed a lot of stuff from here, only relevant attrs showed):

# Mikko K. Kortelainen, Unix, koo.fi
dn: CN=Mikko K. Kortelainen,OU=Unix,DC=koo,DC=fi
cn: Mikko K. Kortelainen
name: Mikko K. Kortelainen
pwdLastSet: 129520857664062022
uid: mikko
uidNumber: 10385
gidNumber: 10019
loginShell: /bin/bash
name: Mikko K. Kortelainen
msSFU30Password:
unixHomeDirectory: /home/user/mikko
msSFU30PosixMemberOf: CN=UNIX users,OU=Unix,DC=koo,DC=fi

Let's look at the group "UNIX users":

# ldapsearch "(cn=UNIX users)"

dn: CN=UNIX users,OU=Unix,DC=koo,DC=fi
cn: UNIX users
msSFU30PosixMember:: ...
msSFU30PosixMember:: ...
msSFU30PosixMember:: ...
msSFU30PosixMember:: ...

The msSFU30PosixMember attributes contain references to the member user accounts. That will be used to map users to groups.

4   Install and configure nss_ldap and pam_ldap

Now, let's install nss_ldap (the name service switch ldap module) and pam_ldap (the PAM ldap module):

# sudo aptitude install libnss-ldap libpam-ldap

Dpkg will ask questions to do some preconfiguring. This configuration will use the Kerberos host keytab:

LDAP server: ldap://dc1.koo.fi ldap://dc2.koo.fi
Distinguished name of the search base: dc=koo,dc=fi
LDAP version to use: 3
Make local root database admin: no
Does the database require login: no
Password hash: md5

Next, let's configure /etc/ldap.conf some more. This configuration file is for both the pam_ldap and nss_ldap. You will need to substitute your own values here. Let's go through it piece by piece.

First some basic information on how to connect to the directory, and timeouts, option to not to be fussy over SSL/TLS certificates and to ignore referrals. If your directory is partitioned you will have to enable referrals. In that case be sure to have connectivity to all domain controllers, as well as a working DNS. The use_sasl on option will change to using Kerberos through SASL. The sasl_auth_id must be the same which you mapped to your machine account with the ktpass Windows command earlier.

The krb5_ccname tells where to find the Kerberos credentials cache file.  The file must be readable by any user you wish to be able to read information from AD. It must also be refreshed periodically. This was configured as a cron job earlier in this article.

use_sasl on
sasl_auth_id host/ubuntu.koo.fi
krb5_ccname FILE:/tmp/krb5cc_host
base dc=koo,dc=fi
uri ldap://dc1.koo.fi ldap://dc2.koo.fi
ldap_version 3
timelimit 30
bind_timelimit 30
tls_checkpeer no
referrals no
bind_policy soft

The nss_base_ attributes tell where to look for particular types of objects in the directory. We will simply give the root object with subtree scope so that the whole directory is checked. You may want to restrict this for performance reasons if you have a huge directory, or for security reasons if you have strict permissions in your directory.

scope sub
nss_base_passwd dc=koo,dc=fi?sub
nss_base_shadow dc=koo,dc=fi?sub
nss_base_group dc=koo,dc=fi?sub

The nss_map_attribute is used to map an ldap attribute to a Linux NSS property. The first argument is the NSS property, and the second argument is the LDAP attribute from which to fetch the value. Be sure to double-check the attribute names that they match the ones in your directory.

nss_map_objectclass posixAccount User
nss_map_objectclass shadowAccount User
nss_map_attribute uid uid
nss_map_attribute uidNumber uidNumber
nss_map_attribute gidNumber gidNumber
nss_map_attribute loginShell loginShell
nss_map_attribute gecos name
nss_map_attribute userPassword msSFU30Password
nss_map_attribute homeDirectory unixHomeDirectory
nss_map_attribute shadowLastChange pwdLastSet
nss_map_objectclass posixGroup Group
nss_map_attribute uniqueMember msSFU30PosixMember
nss_map_attribute cn cn
nss_initgroups_ignoreusers backup,bin,clamav,daemon,dovecot,dspam,games,gnats,irc,landscape,libuuid,list,lp,mail,man,messagebus,mysql,nagios,news,ntp,openamq,postfix,postgres,proxy,rabbitmq,root,sshd,statd,sync,sys,syslog,uucp,www-data

The pam_login_attribute is used as the user name for the PAM authentication process. The pam_password says which kind of a protocol to use for changing user passwords if that is needed. You need to install libpam-ldap to take advantage of it.

pam_login_attribute msSFU30Name
pam_filter objectclass=user
pam_password ad

The last change is needed to the file /etc/nsswitch.conf so that the Name Service Switch knows it should be querying AD:

passwd: compat ldap
group: compat ldap
shadow: compat ldap

Only those three lines need editing.

At this stage, you should be able to list users from Active Directory using "getent passwd", and groups using "getent group". If you can't, add line "debug 1" to /etc/ldap.conf and try re-running "getent passwd". It should give you a pretty verbose log of what's going on. You can set the verbosity up to 10 if you like.

If you want to cache the results, you can install the "unscd" package. That will cache users and groups, so that they are not asked from LDAP every time, which makes things faster. You can tune the settings in /etc/nscd.conf.

5   PAM configuration

Now that the NSS part is working properly, let's configure PAM. It should mostly be done automatically already, just double-check it.

This is what my /etc/pam.d/common-auth looks like:

auth   [success=3 default=ignore]  pam_krb5.so minimum_uid=1000
auth    [success=2 default=ignore]  pam_unix.so nullok_secure try_first_pass
auth    [success=1 default=ignore]  pam_ldap.so use_first_pass
auth    requisite           pam_deny.so
auth    required            pam_permit.so

That will enable Kerberos, LDAP and Unix passwd file authentication.

This is /etc/pam.d/common-account:

account    [success=2 new_authtok_reqd=done default=ignore]    pam_unix.so
account [success=1 default=ignore]  pam_ldap.so
account requisite           pam_deny.so
account required            pam_permit.so
account required            pam_krb5.so minimum_uid=1000

/etc/pam.d/common-password:

password   requisite           pam_krb5.so minimum_uid=1000
password    [success=2 default=ignore]  pam_unix.so obscure use_authtok try_first_pass sha512
password    [success=1 user_unknown=ignore default=die] pam_ldap.so use_authtok try_first_pass
password    requisite           pam_deny.so
password    required            pam_permit.so

/etc/pam.d/common-session:

session [default=1] pam_permit.so
session requisite  pam_deny.so
session required   pam_permit.so
session optional   pam_umask.so
session optional   pam_krb5.so minimum_uid=1000
session required   pam_unix.so
session required   pam_mkhomedir.so skel=/etc/skel/ umask=0077
session optional   pam_ldap.so
session optional   pam_ck_connector.so nox11

The only thing I had to change was to add the pam_mkhomedir.so line, which creates a home directory automatically for any user logging in the first time.

6   The End Result

After all that is in place, you should be able to log in locally, or remotely with ssh, using a user account from AD, authenticating to AD with Kerberos, and get your home directory created automatically for you should it not exist already.