Integrating Ubuntu with Active Directory

     

Update: please refer to Noobuntu – Enterprise Ubuntu development environment with Active Directory integration for up-to-date information.

Prelude

You can run, but you can’t hide, sooner or later it’ll knock on your door. I was assigned with the task of providing our colleagues with Linux workstations. Previously we had Windows, which is no biggie. Then came OS X, which is far from being perfect, but still reasonably doable. And then came Linux…

The problem is that you have thousands of distros, each of which has dozens of releases, and even within the same release there are package updates that often change behaviour. So when you read a tutorial, you can never be sure the things it says will apply the same way to YOUR specific distro and release. Not to mention bugs… there are sooo many things that are supposed to work in a certain way but just refuse to. During making this thing, I faced countless bugs that I had to work around in some way. It’s terrible, really. I understand that Linux gives you endless possibilites, I give it that, but at the same time it sure as hell gives you the most bugs, by far.

Anyway, I’m not here to complain. I’m here to tell you how to do this on your own. The things you’ll achieve by the end of this article:

  • AD authentication with cached credentials, so that the colleague can use the workstation (laptop) at home, too
  • AD sudo rules that are also cached
  • Full disk encryption that you can unlock with an USB key, but which also has a recovery key stored in AD in case the USB key gets lost/stolen

Installation

This guide assumes you use Ubuntu 15.10 x64. If you’re using a recent laptop with Nvidia card in it, chances are, Ubuntu won’t even boot. Yeah, we’re off to a great start. In this case you’ll have to hit the ‘e’ key when Grub shows up, and add this to the boot command:

nouveau.modeset=0

Then hit F10 to boot. If you’re dealing with many workstations, it’s probably a good idea to add this to the installer itself so that you don’t have to do this all the time. To do this, use LiLi and after you applied to ISO, edit the boot/grub.cfg file accordingly.

When installing, make sure to download updates so that maybe you’ll hit fewer bugs afterwards. Also choose to encrypt disks if that’s your requirement – save yourself some time until you set up USB unlock and use a very-very simple password for that, you’ll remove this key with a much stronger one later.

Once the install finishes and you reboot, you’ll have to add that nouveau command line to Grub again (if you had to earlier). After startup, the first thing you wanna do is probably:

apt-get update
apt-get dist-upgrade
apt-get install mc synaptic

Then also enable the proprietary Nvidia driver from the control panel, and reboot.

Authentication

For this to work, you need to install a few packages:

apt-get -y install samba sssd ntp
DEBIAN_FRONTEND=noninteractive apt-get -y install krb5-user

Why the second line? Because otherwise it asks you certain questions, but for whatever reason sometimes it “forgets” to ask them all, so we won’t even bother with that and set up the related file on our own instead.

Speaking of files, let’s prepare them:

cp /etc/krb5.conf /etc/krb5.conf.orig
cp /etc/samba/smb.conf /etc/samba/smb.conf.orig
echo "" > /etc/krb5.conf
echo "" > /etc/samba/smb.conf
echo "" > /etc/sssd/sssd.conf
chown root:root /etc/sssd/sssd.conf
chmod 600 /etc/sssd/sssd.conf

Kerberos

Now edit the empty /etc/krb5.conf file like this (ad.foobar.com is your domain, and dc1, dc2 are the domain controllers):

[libdefaults]
    default_realm = AD.FOOBAR.COM

# The following krb5.conf variables are only for MIT Kerberos.
    krb4_config = /etc/krb.conf
    krb4_realms = /etc/krb.realms
    kdc_timesync = 1
    ccache_type = 4
    forwardable = true
    proxiable = true

# The following libdefaults parameters are only for Heimdal Kerberos.
    v4_instance_resolve = false
    v4_name_convert = {
        host = {
            rcmd = host
            ftp = ftp
        }
        plain = {
            something = something-else
        }
    }
    fcc-mit-ticketflags = true

[realms]
    AD.FOOBAR.COM = {
        kdc = DC1.AD.FOOBAR.COM
        kdc = DC2.AD.FOOBAR.COM
        admin_server = DC1.AD.FOOBAR.COM
    }

[login]
    krb4_convert = true
    krb4_get_tickets = false

NTP

Find all the lines starting with server in /etc/ntp.conf and replace them with your DCs:

server dc1.ad.foobar.com
server dc2.ad.foobar.com

This is important for Kerberos to work since it greatly depends on time.

Samba

Now set up the empty /etc/samba/smb.conf with the following content:

[global]
   workgroup = AD
   client signing = yes
   client use spnego = yes
   kerberos method = secrets and keytab
   realm = AD.FOOBAR.COM
   security = ads

   server string = %h server (Samba, Ubuntu)
   dns proxy = no
   log file = /var/log/samba/log.%m
   max log size = 1000
   syslog = 0
   panic action = /usr/share/samba/panic-action %d
   server role = standalone server
   passdb backend = tdbsam
   obey pam restrictions = yes
   unix password sync = yes
   passwd program = /usr/bin/passwd %u
   passwd chat = *Enter\snew\s*\spassword:* %n\n *Retype\snew\s*\spassword:* %n\n *password\supdated\ssuccessfully* .
   pam password change = yes
   map to guest = bad user
   usershare allow guests = yes

SSSD

Your /etc/sssd/sssd.conf should look like this:

[sssd]
services = nss, pam, sudo
config_file_version = 2
domains = AD.FOOBAR.COM

[domain/AD.FOOBAR.COM]
id_provider = ad
access_provider = ad
cache_credentials = True
override_homedir = /home/%d/%u
default_shell = /bin/bash

Naturally, you’re free to use whatever shell you prefer.

Hostname

Your /etc/hostname will contain a line like this:

127.0.0.1 foo32linux

Where foo32linux is the workstation’s hostname. Modify that like this:

127.0.0.1 foo32linux.ad.foobar.com foo32linux

This setting is crucial for Samba to create a DNS record for the workstation in AD.

PAM

Now find the following line in /etc/pam.d/common-session:

session required pam_unix.so

And put this line after that:

session required pam_mkhomedir.so skel=/etc/skel/ umask=0077

This will make it possible to generate a home folder for AD users when they first log in. The umask above will make your users unable to browse other users’ files, but you can use whatever umask you want.

LightDM

Add these to your /usr/share/lightdm/lightdm.conf.d/50-unity-greeter.conf file:

greeter-show-manual-login=true
greeter-hide-users=true

And also to /usr/share/lightdm/lightdm.conf.d/50-ubuntu.conf:

allow-guest=false

These options are quite self-explanatory; they will allow you to enter a username that doesn’t exist on the system yet, the login screen will not show recently logged in users and won’t allow guests to use the computer.

Domain join

Now it’s time to apply the changes you made and actually join the computer to the domain.

systemctl restart smbd.service
systemctl restart nmbd.service
systemctl restart ntp.service

Once they all restarted successfully, do the join:

sudo kinit Administrator
sudo klist
sudo net ads join -k

Once that also succeeds, restart the SSSD service:

systemctl restart sssd.service

If all is good, you can test AD auth with something like this:

su - stewie.griffin

Where stewie.griffin is an AD user. This should tell you that it’s created a new home folder for the user and also give you a shell. Congratulations, you just got AD auth working.

Sudo

AD

For sudo to work for AD users you need to extend the AD schema first. Grab the latest release of sudo and get the doc/schema.ActiveDirectory file.

If you’re playing it safe, you prolly also want to check your Ubuntu’s sudo version (dpkg -l | grep sudo) and see if that schema file is any different from the latest.

Now it’s time for import (make sure to modify the domain path according to your domain name):

ldifde -i -f schema.ActiveDirectory -c "CN=Schema,CN=Configuration,DC=X" "CN=Schema,CN=Configuration,DC=ad,DC=foobar,DC=com" -j .

You need to do this on the “schema master” DC, which is most likely the first DC in your domain. If you don’t do it on the schema master, you’ll get a nice little error like this (I learned it the hard way):

The server side error is: 0x202b A referral was returned from the server.

But on the schema master it’ll work just fine. Verify it with ADSI Edit, open the Schema naming context and look for the sudoRole class.

Now create the sudoers OU on your domain root. You can use other paths, but then you’ll need to modify your SSSD config, so I recommend you to stick with this. This OU will hold all the sudo settings for all your Linux workstations. You can organize it any way you want, you can create per-computer or per-user rules, or whatever you want. This guide will use per-computer rules.

So, under this OU, create a sudoRole object. To create the sudoRole object you have to use ADSI Edit, but once created, you can use Active Directory Users and Computers to modify it.

Let’s assume I have a computer named foo32linux, a user called stewie.griffin and I want to let him run all commands with sudo on that comp. In this case, I create a sudoRole object under the sudoers OU. For the sudoRole you can use any name you want – I stick with the computer name since I use per-computer rules. Now set its attributes as follows:

  • sudoHost: foo32linux (see Update 2 on the bottom)
  • sudoCommand: ALL
  • sudoUser: stewie.griffin

For commands you can use specific entries as well, like /bin/less or whatever. These are just the most crucial attributes, but there’s more. For example, to make sudo work without asking for a password, you could set sudoOption to !authenticate.

That’s it for the AD side of things.

Linux

SSSD refreshes its local cache with the updated rules every few hours, but the simplest way to test it is to just reboot the computer. After that, log in with stewie.griffin and check if the newly created sudo rules really made their way to the comp:

sudo -l

It should list all the related entries you added to that user and computer. Easy-peasy!

Encryption

Keys

For unlocking the encrypted disk you’ll have both a USB key and a recovery key which you back up to AD.

For the recovery key you probably want something that’s easy to type. This key construct resembles FileVault (OS X) keys:

#!/bin/sh
for i in 1 2 3 4 5
do
    base64 /dev/urandom | head -c 4 | tr 'a-z' 'A-Z' | tee -a key-ad.txt
    echo -n '-' | tee -a key-ad.txt
done
base64 /dev/urandom | head -c 4 | tr 'a-z' 'A-Z' | tee -a key-ad.txt

For the USB key:

#!/bin/sh
base64 /dev/urandom | head -c 1024 | tee key-usb.txt

Now you need to find the partition used for encryption. You can look for it in gparted, or check out /etc/crypttab. Then check it (assuming your is /dev/sda3):

cryptsetup -v luksDump /dev/sda3

It should print one key slot being used (the one you specified during install) and some other diagnostic info. If it’s not the one, it will say something like this:

Device /dev/sda1 is not a valid LUKS device.

So you can’t miss it. Once you figured out the partition, add the 2 generated keys to its store:

cryptsetup luksAddKey /dev/sda3 key-ad.txt
cryptsetup luksAddKey /dev/sda3 key-usb.txt

USB

Get an empty thumb drive and create a single ext2 partion on it. Set its label to KEY. Now put the USB key file on it and set up permissions:

mv key-usb.txt /media/$SUDO_USER/KEY/.keyfile
chown root.root /media/$SUDO_USER/KEY/.keyfile
chmod 0400 /media/$SUDO_USER/KEY/.keyfile

AD

In case your USB key is lost, you obviously don’t want to lose access to the computer. For this reason, grab the key from key-ad.txt and save it to the computer object using the MacLocker utility – this util will save the given key as a BitLocker password, given your AD is prepared for saving BitLocker info. It was originally intended for Mac FileVault keys, but works with any other keys just fine.

Setup

Create the /root/unlock.sh script:

#!/bin/sh

ask_for_password () {
#    cryptkey="Unlocking the disk $cryptsource ($crypttarget)\nEnter passphrase: "
    cryptkey="Unlocking the disk $crypttarget\nEnter passphrase or insert USB key, then press Return: "
    if [ -x /bin/plymouth ] && plymouth --ping; then
        cryptkeyscript="plymouth ask-for-password --prompt"
        cryptkey=$(printf "$cryptkey")
    else
        cryptkeyscript="/lib/cryptsetup/askpass"
    fi
    $cryptkeyscript "$cryptkey"
}

device=$(echo $1 | cut -d: -f1)
filepath=$(echo $1 | cut -d: -f2)

# Ask for password if device doesn't exist
if [ ! -b $device ]; then
    ask_for_password
    exit
fi

mkdir /tmp/auto_unlocker
mount $device /tmp/auto_unlocker

# Again ask for password if device exist but file doesn't exist
if [ ! -e /tmp/auto_unlocker$filepath ]; then
    ask_for_password
else
    cat /tmp/auto_unlocker$filepath
fi

umount /tmp/auto_unlocker

This will be used upon boot to let you use an USB key and also a password in case the USB key is missing. Yes, there’s really no built-in method for this.

Now open /etc/crypttab and find the line for your encrypted partition (/dev/sda3 in our example):

sd3_crypt UUID=(...) none luks,discard

Modify that like this:

sd3_crypt UUID=(...) /dev/disk/by-label/KEY:/.keyfile luks,keyscript=/root/unlock.sh

The unlock.sh script is in fact put into the initrd, so it’s not read from /root/unlock.sh, this only tells the system to copy it from that location when compiling the initrd.

First create a backup, so that in case something goes wrong, you can still revert to the old setup from the Grub command line by appending “.orig” to the initrd path:

cp /boot/initrd.img-$(uname -r)  /boot/initrd.img-$(uname -r).orig
cp /etc/crypttab /etc/crypttab.orig
cp /etc/initramfs-tools/modules /etc/initramfs-tools/modules.orig

Now actually update the initrd with your crypttab changes:

update-initramfs -u

If you can’t boot your system at all after this, you can also open the disk from a live Ubuntu system:

mkdir /mnt/recovery
cryptsetup luksOpen /dev/sda3 /mnt/recovery

Updating the initrd isn’t enough just yet – there’s a serious bug that causes a 90 seconds delay during boot. So you have to disable LUKS system-wide to avoid it. Open /etc/default/grub and find the following line:

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"

Change this to:

GRUB_CMDLINE_LINUX_DEFAULT="quiet splash luks=no"

Then update Grub:

update-grub

Then reboot and verify that you can boot up using both the USB key and the recovery key you saved to the AD computer object.

In that case it’s time to delete the old dummy unlock password you used during install:

cryptsetup luksRemoveKey /dev/sda3

Once done, all that’s left is to enjoy the AD integrated Ubuntu workstation you just set up.

Acknowledgements

This guide wouldn’t have been possible without the help and hints of these articles and/or threads:

Thanks, guys!

Update: there’s also a bunch of scripts to automate all this: ubuntu-ad.

Update 2: starting with Ubuntu 16.04 (SSSD 1.13), sudoHost must be in FQDN form, e.g. foo32linux.ad.foobar.com. In AD, it’s the dNSHostName attribute of the computer object.