Integrated Windows Authentication (IWA) is a useful feature for intranets, where a web browser on a Windows client joined to Active Directory (AD) can seamlessly pass authentication information to a web server - without needing to prompt the user for a password. It's supported by IE, Firefox and Chrome on the client side, and naturally by IIS on the server side. With just a little bit of effort, it can also be supported by Apache on a Linux or other Unix-type OS, and I'll take a look at doing that here.
IWA is a generic term that covers a few different protocols. One is the older NTLM Authentication, which can be setup on a Linux server with mod_auth_ntlm_winbind, but that's awfully clunky and requires setting up and running Samba's winbindd daemon. Another is Kerberos, which is fairly well supported in must Linux/Unix distros, and is an integral part of Active Directory.
There are a lot of writeups on integrating a Linux box with AD, but most of them get very complicated, trying to integrate everything, including login, file sharing, group mapping, etc. And deeply relying on Samba. I'm going to focus on just the one simple task of HTTP authentication in Apache, not using Samba, and being as explicit as possible on what needs to be done on both the Linux and Windows Active Directory sides of the setup.
I'm going to do this for Ubuntu 10.04 and assume you have root access and are familiar with general Apache configuration. Other Linux distros or perhaps BSDs should be very very similar.
Some other things you're going to need to be able to do, or at least get someone in you organization to do for you are:
setspn and ktpass Windows commandline utilities against the User object you createFor the rest of this, we're going to assume:
ad.foobar.eduAD.FOOBAR.EDU (usually the domain-name uppercased)test.foobar.edu1.2.3.41.2.3.4 results in test.foobar.eduFirstly, we need a User object in Active Directory that will represent the Apache service, and will hold a password which Kerberos tickets will be based on.
In the Active Directory Users and Computers utility, create a User object, the name doesn't matter much, so I'll go with Test-HTTP

after hitting Next >, on the password page uncheck User must change password... and check Password never expires. Go ahead and enter anything as a password, it'll get changed to something random in a later step.

Go ahead and finish that up.
Next, we need to associate a Service Principal Name (SPN) with the User object we just created. Kerberos principals are usually <protocol>/<domain-name>@<kerberos-realm>. Since we're doing a web server, it'll be known in Kerberos as HTTP/test.foobar.edu@AD.FOOBAR.EDU Run this in a Command Prompt window:
setspn -A HTTP/test.foobar.edu Test-HTTP
(note that we left off the @AD.FOOBAR.EDU part, setspn knows to put that in)
Lastly, we're going to create a keytab file (I'll call it test-http.keytab), which holds encryption keys based on the User object's password. When a client requests a ticket to access our Linux box, AD will locate the User object based on the SPN we associated with it, and use the same encryption keys to create the Kerberos tickets our Linux's Apache will be setup to require.
(This is a one-line command, but I'm going to display it below as several lines for readability)
ktpass -out test-http.keytab
-princ HTTP/test.foobar.edu@AD.FOOBAR.EDU
-mapuser Test-HTTP
-mapOp set
+rndPass
-crypto All
-ptype KRB5_NT_PRINCIPAL
The +rndPass changes the User objects password to something random, you don't need to know what it is - the keytab is the thing you really care about here.
Securely copy that test-http.keytab to the Linux box, and delete it off the Windows machine. We're done with AD now, back to the real world...
Move the keytab file somewhere handy, such as /etc/apache2/test-http.keytab, and set the permissions so that the Apache process (and nobody else) has access:
chmod 440 test-http.keytab chown www-data:www-data test-http.keytab
Install the Apache Kerberos module
aptitude install libapache2-mod-auth-kerb
You'll need an /etc/krb5.conf file. A simple one that leaves it up to Kerberos to discover what it needs might be as simple as:
[libdefaults] default_realm = AD.FOOBAR.EDU
Here's a more explicit one that specifies Active Directory KDCs (Key Distrubution Centers), by IP
[libdefaults]
default_realm = AD.FOOBAR.EDU
default_keytab_name = FILE:/etc/krb5.keytab
[realms]
AD.FOOBAR.EDU = {
kdc = 1.2.0.1
kdc = 1.2.0.2
kdc = 1.2.0.3
default_domain = AD.FOOBAR.EDU
}
[domain_realm]
.foobar.edu = AD.FOOBAR.EDU
That sort of thing is documented on the MIT website.
We're in the home stretch now, Apache directives to protect a cgi-bin directory for example might look like:
ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
<Directory "/usr/lib/cgi-bin">
AllowOverride None
Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
Order allow,deny
Allow from all
AuthName "FOOBAR Active Directory"
AuthType KerberosV5
KrbServiceName HTTP
Krb5Keytab /etc/apache2/test-http.keytab
require valid-user
</Directory>
Those last 5 lines inside the <Directory> block are the key here. The KrbServiceName of HTTP corresponds to what we entered as the protocol part of the principal name back on the setspn and ktpass commands. The AuthName is what will be displayed if the browser falls back to basic autentication if Kerberos is not available.
Here's a super-simple CGI test.sh script we can put in our Kerberos-protected cgi-bin directory, be sure to make it executable.
#!/bin/sh echo 'Content-Type: text/plain' echo echo You are: $REMOTE_USER
Go to a Windows client signed into Active Directory. To get IE or Chrome to attempt Kerberos authentication you'll have to add test.foobar.edu to the Local Intranet in the Internet Settings control panel. Here are some shots of where to go:



For Firefox, you'll want the NTLMAuth add-in, which lets you specify which domains that Firefox should attempt Kerberos authentication with.
Once you've got the browser fixed up, try accessing http://test.foobar.edu/cgi-bin/test.sh, and if everything works out, you should be rewarded with something like:
You are: bob.smith@AD.UND.EDU
If you didn't follow the steps to configure the browser to attempt Kerberos auth with the site, the browser should pop-up a userid/password box, and if you enter the correct info, it should show the same info.
So there you have it, after 15 minutes of work you can now have a webpage tell you what your own AD userid is. OK, it's probably more useful than that - you can now write webapps proxied behind Apache, such as a Django app, that just have to look at the REMOTE_USER variable to tell who's on the other end of the connection.
You'll probably not want to require Kerberos auth for the whole app, but all you really need is to require Kerberos for one particular login URL that sets your userid into a session, and leave it up to the framework to check the session for authentication on the rest of the site.
Previously I wrote about getting Debian GNU/kFreeBSD working in a jail. I've worked on it a bit more, polishing things up so I've got it working pretty seamlessly with my existing ezjail FreeBSD jails, so everything starts automatically, and you can use the ezjail commands to stop/restart the jail.
Here are a few more notes about how things got setup for my jail I named debian:
In /boot/loader.conf, I added these lines:
fdescfs_load="YES" linprocfs_load="YES" linsysfs_load="YES" tmpfs_load="YES"
Created /etc/fstab.debian and populated with:
linproc /jails/debian/proc linprocfs rw 0 0 linsys /jails/debian/sys linsysfs rw 0 0 tmpfs /jails/debian/lib/init/rw tmpfs rw 0 0
Created /usr/local/etc/ezjail/debian with these contents:
export jail_debian_hostname="debian" export jail_debian_ip="127.0.0.6" export jail_debian_interface="lo0" export jail_debian_rootdir="/jails/debian" export jail_debian_mount_enable="YES" export jail_debian_devfs_enable="YES" export jail_debian_devfs_ruleset="devfsrules_jail" export jail_debian_fdescfs_enable="YES" export jail_debian_exec_start="/etc/init.d/rc 3" export jail_debian_flags="-l -u root"
I also tried adding an IPv6 address to the jail, and that seems to work OK
So you can now stop/start with jail with
service ezjail.sh stop debian service ezjail.sh start debian
If you create a symlink for login (so that from the jail's POV there's a /usr/bin/login, like there would be on a FreeBSD jail)
cd /jails/debian/usr/bin/ ln -s ../../bin/login .
then you can use the ezjail-admin command to get a console in the jail, with:
ezjail-admin console debian
Otherwise, I've been using my own script to get a console (which assumes bash is installed in the jail), named /usr/local/sbin/jlogin
#!/bin/sh
#
# log into a jail, running bash
#
JID=`jls | grep " $1 " | awk '{print $1}'`
exec jexec $JID env -i PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin TERM=$TERM EDITOR=$EDITOR LANG=$LANG HOME=/root bash -l
That runs as:
jlogin debian
I've been a FreeBSD user for quite some time, going back to 3.3 or so, and for the last serveral years have also been working a lot with Ubuntu Linux. So when I ran across Debian GNU/kFreeBSD, which provides a Debian environment on top of a FreeBSD kernel, I was somewhat intrigued. It got even more interesting when I found a tutorial on setting up GNU/kFreeBSD in a jail. The notion of having a Debian environment on my home FreeBSD server without having to get something like VirtualBox running was just too good to pass up.
I got it running fairly decently, but along the way ran into some small problems - and thought I'd jot down what they were and what the fixes were.
At first, I was using FreeBSD 8.2-RELEASE, and used debootstrap to install Debian Squeeze, as the tutorial showed. Once inside the jail, things sort of worked, but most commands, aptitude especially, would die with:
User defined signal 1
It turns out you need a newer kernel than 8.2 to run kFreeBSD in a chroot, as is mentioned in the FAQ. I upgraded my FreeBSD kernel/world to 8.3-PRERELEASE (2012-02-22), and the "signal 1" problem went away.
The next problem was that aptitude would still die, with:
Uncaught exception: Unable to read from stdin: Operation not permitted
After reading about this bug in cwidget, it seemed an upgrade to Wheezy was needed to fix the problem - and sure enough that problem went away.
The upgrade to Wheezy didn't go entirely smoothly, mainly due to the kbdcontrol package (required by sysvinit) being unable to access /dev/console in the jail. I wasn't worried about keeping things in the jail isolated for security reasons, so I went ahead and added /dev/console on-the-fly to the running jail by running outside the jail:
devfs -m /jails/debian/dev rule add path 'console*' unhide devfs -m /jails/debian/dev rule applyset
After that, the kbdcontrol package was able to be upgraded, and I seem to have a Wheezy FreeBSD jail now. Very cool.
UPDATE: A followup talks more about the actual file changes made to run as an ezjail
Fooling around a bit more with accessing a VM's serial console from a KVM hypervisor with
virsh console mymachine
I found one thing that doesn't carry over from the host to the VM is the terminal window size, so if you try to use something like vim through the console connection, it seems to assume a 80x25 or so window, and when vim exits your console is all screwed up.
It looks like a serial connection doesn't have an out-of-band way of passing that info the way telnet or ssh does, so you have set it manually. You can discover your settings on the host machine with
stty size
which should show something like:
60 142
on the VM, the same command probably shows
0 0
zero rows and columns, no wonder it's confused. Fix it by setting the VM to have the same rows and columns as the host with something like:
stty rows 60 columns 142
and you're in business.
So I've been running Ubuntu 10.04 server virtual machines on a host running KVM as the hypervisor, and thought I should take a look at accessing the VM's console from the host, in case there's a problem with the networking on the VM.
The hosts's VM libvirt definition shows a serial port and console defined with
<serial type='pty'> <source path='/dev/pts/1'/> <target port='0'/> <alias name='serial0'/> </serial> <console type='pty' tty='/dev/pts/1'> <source path='/dev/pts/1'/> <target type='serial' port='0'/> <alias name='serial0'/> </console>
and within the stock Ubuntu 10.04 server VM, dmesg | grep ttyS0 shows:
[ 0.174722] serial8250: ttyS0 at I/O 0x3f8 (irq = 4) is a 16550A [ 0.175027] 00:05: ttyS0 at I/O 0x3f8 (irq = 4) is a 16550A
So the virtual hardware is all setup on both ends, but ps aux | grep ttyS0 doesn't show anything
We need to have a process listening to that port. To do that, create a file named /etc/init/ttyS0.conf with these contents:
# ttyS0 - getty # # This service maintains a getty on ttyS0 from the point the system is # started until it is shut down again. start on stopped rc RUNLEVEL=[2345] stop on runlevel [!2345] respawn exec /sbin/getty -L 38400 ttyS0 xterm-color
and then run
initctl start ttyS0
back in the host machine run virsh list to find the name or id number of your VM, and then
virsh console <your-vm-name-or-number>
to connect, hit return and you should see a login prompt.
I was playing with creating and cloning Ubuntu virtual machines the other day, and got to the point where I had a nicely setup reference image that I could just copy to fire up additional VMs that would be in a pretty usable state.
There are a few things within a cloned VM that you'd want to change if you were going to keep the new instance around, such as the hostname, SSH host keys, and disk UUIDs. I threw together a simple shell script to take care of these things automatically.
#!/bin/sh # # Updates for cloned Ubuntu VM # # # Some initial settings cloned from the master # ROOT=/dev/vda1 SWAP=/dev/vdb1 LONG_HOSTNAME=ubuntu.local SHORT_HOSTNAME=ubuntu if [ -z $1 ] then echo "Usage: $0 <new-hostname>" exit 1 fi # # Update hostname # shorthost=`echo $1 | cut -d . -f 1` echo $1 >/etc/hostname hostname $1 sed -i -e "s/$LONG_HOSTNAME/$1/g" /etc/hosts sed -i -e "s/$SHORT_HOSTNAME/$shorthost/g" /etc/hosts # # Generate new SSH host keys # rm /etc/ssh/ssh_host_* dpkg-reconfigure openssh-server # # Change root partition UUID # OLD_UUID=`blkid -o value $ROOT | head -n 1` NEW_UUID=`uuidgen` tune2fs -U $NEW_UUID $ROOT sed -i -e "s/$OLD_UUID/$NEW_UUID/g" /etc/fstab /boot/grub/grub.cfg # # Change swap partition UUID # OLD_UUID=`blkid -o value $SWAP | head -n 1` NEW_UUID=`uuidgen` swapoff $SWAP mkswap -U $NEW_UUID $SWAP swapon $SWAP sed -i -e "s/$OLD_UUID/$NEW_UUID/g" /etc/fstab # # Remove udev lines forcing new MAC address to probably show up as eth1 # sed -i -e "/PCI device/d" /etc/udev/rules.d/70-persistent-net.rules sed -i -e "/SUBSYSTEM==/d" /etc/udev/rules.d/70-persistent-net.rules echo "UUID and hostname updated, udev nic lines removed, be sure to reboot"
I'd then run it on the cloned machine with something like
update_clone.sh mynewmachine.foobar.com
This somewhat particular to my specific master VM, in that it's expecting one disk dedicated to root and one disk dedicated to swap, and the VM was created with ubuntu.local as the hostname. Hopefully though it'll give some ideas about what to look for and how to script those changes.
If you're fooling around with various OSes, installing them by first burning CDs or DVDs gets to be a drag - and you end up with piles of old discs that just go into a landfill. Sure, there are rewritable disks, but they wear out and get scratched eventually. USB memsticks can be painful too - sometimes difficult to create and with different BIOSes having different levels of support.
A slick way to go is to set yourself up to do PXE (Preboot eXecution Environment) installations over a network. Most network cards have had PXE support included for many years now. If you have a machine handy that can act as a simple server, you can have an enviroment where you boot a machine, select the OS you want to install from a menu, and everything will just be pulled over your local network.
There are plenty of writeups on how to PXE install Ubuntu from an Ubuntu server, or FreeBSD from a FreeBSD server - but to make things more interesting and explicit I'll go cross-platform and talk about deploying Ubuntu Server 11.04 from a FreeBSD 8.2 server, and try to make it general enough so that later on we can add other OSes to the menu such as CentOS or OpenBSD.
PXE booting a machine requires two basic services be present on your network:
DHCP - to assign the booted machine an IP address and tell it what "network bootstrap program" (NBP) to fetch from a TFTP server
TFTP (Trivial FTP - not to be confused with regular FTP) serves up the initial boot files
OSes such as Ubuntu or CentOS require a third service:
For the Network Bootstram Program, we'll use PXELINUX, which is available as part of the SYSLINUX project. The name SYSLINUX is a bit misleading in that it's not actually Linux, but rather a collection of bootloaders that are often used with Linux, and capable of loading other OSes as well. Think of something more along the lines of GRUB, than an actual Linux distro.
To start off with, I'll create a /tftpboot directory, download
syslinux-4.04.tar.gz from here, extract and
copy two files we want:
mkdir /tftpboot fetch http://www.kernel.org/pub/linux/utils/boot/syslinux/syslinux-4.04.tar.gz tar xzvf syslinux-4.04.tar.gz cp syslinux-4.04/core/pxelinux.0 /tftpboot cp syslinux-4.04/com32/menu/menu.c32 /tftpboot
We're done with the syslinux download now, so you could clean it up if you want with:
rm -rf syslinux-4.04*
Next, create a configuration directory
mkdir /tftpboot/pxelinux.cfg
and in that directory create a file named default with these initial
contents:
DEFAULT menu.c32
PROMPT 0
TIMEOUT 200
LABEL local
MENU LABEL Local Boot
LOCALBOOT 0
That should be enough to get us a barebones menu when we PXE boot a machine, with a single option to boot off the local harddisk (we'll get to Ubuntu later).
TFTP is already included in FreeBSD, just need to make sure it's enabled.
In /etc/inetd.conf make sure this line has the default # removed from the
front (so it's not commented out)
tftp dgram udp wait root /usr/libexec/tftpd tftpd -l -s /tftpboot
In /etc/rc.conf, make sure inetd is enabled, adding if necessary:
inetd_enable="YES"
Depending on what you had to do above, start, or reload the inetd daemon with:
service inetd start
or
service inetd reload
Check that the machine is now listing on UDP port 69
sockstat | grep :69
See if you can fetch the NBP using the tftp utility (assuming your
server's IPv4 address on the network you'll be doing PXE boots is
10.0.0.1)
cd /tmp tftp 10.0.0.1 tftp> get /pxelinux.0 tftp> quit rm pxelinux.0
If it works you should have seen somthing like:
Received 26443 bytes during 0.1 seconds in 53 blocks
For this part I'm assuming you're running an ISC dhcpd server (if not,
we'll have to cover that in another post). You basically just need to
add two lines to /usr/local/etc/dhcpd.conf telling a client what
server to use for TFTP and what NBP to fetch:
next-server 10.0.0.1; filename "/pxelinux.0";
On my server, I just wanted to do this on one particular subnet, so there's a chunk that looks something like this now:
subnet 10.0.0.0 netmask 255.255.255.0
{
range 10.0.0.127 10.0.0.250;
option routers 10.0.0.1;
next-server 10.0.0.1;
filename "/pxelinux.0";
}
Restart dhcpd
service isc-dhcpd restart
On your client machine, you may have to poke around in the BIOS to enable PXE booting. You'll have to figure out this part for yourself. If you can select your Network Card as the boot device, and everything else is working right, you should see a simple menu something like this:

OK! we're at the "Hello World" stage, we know the client and server are doing the bare minimum necessary for PXE to function at all. Time to move on to the good stuff.
For this next step, I'll assume you've downloaded an ISO into say
/foo/ubuntu-11.04-server-amd64.iso The specific version shouldn't matter too
much, so if you want to do 10.04 LTS or something else, it should all be
about the same.
Mount the ISO image, so we can copy a couple files into /tftpboot and
share the rest with a web server.
mkdir -P /iso_images/ubuntu-11.04-server-amd64 mount -t cd9660 /dev/`mdconfig -f /foo/ubuntu-11.04-server-amd64.iso` /iso_images/ubuntu-11.04-server-amd64 mkdir /tftpboot/ubuntu-11.04-server-amd64 cp /iso_images/ubuntu-11.04-server-amd64/install/netboot/ubuntu-installer/amd64/linux /tftpboot/ubuntu-11.04-server-amd64 cp /iso_images/ubuntu-11.04-server-amd64/install/netboot/ubuntu-installer/amd64/initrd.gz /tftpboot/ubuntu-11.04-server-amd64
So now our /tftpboot directory has these five files underneath it:
pxelinux.0 pxelinux.cfg/default menu.c32 ubuntu-11.04-server-amd64/linux ubuntu-11.04-server-amd64/initrd.gz
To the /tftpboot/pxelinux.cfg/default file append
LABEL ubuntu-11.04-server-amd64-install
MENU LABEL Ubuntu 11.04 Server AMD64 Install
kernel ubuntu-11.04-server-amd64/linux
append vga=788 initrd=ubuntu-11.04-server-amd64/initrd.gz
Try PXE booting your client again, this time you'll have "Ubuntu 11.04 Server AMD64 Install" as one of your choices, select that, cross your fingers, and if all goes well in a few seconds you should see:

and you can go through and answer the initial questions about the install.
If you're OK with pulling the bulk of the OS over the internet from the official Ubuntu mirrors, it should work although it might be slow. Since we have a nice server sitting on our LAN with a copy of the ISO, we should setup to use that and do a much faster install.
For this example, I'll assume nginx has been installed as the webserver
(any one will do though, so if you've already got apache installed - that'll
work fine too).
The default nginx install uses /usr/local/www/nginx as its docroot, lets
put a symlink to our mounted ISO image in there:
ln -s /iso_images/ubuntu-11.04-server-amd64 /usr/local/www/nginx
and also put in a minimal Debian Installer "preseed" file in there that'll
help things along by telling the installer to use our webserver for
the installation packages. Create a text file named /usr/local/www/nginx/ubuntu-11.04-server-amd64.txt with these contents:
d-i mirror/country string manual d-i mirror/http/hostname string 10.0.0.1 d-i mirror/http/directory string /ubuntu-11.04-server-amd64 d-i mirror/http/proxy string
Check that you can fetch that file with the URL: http://10.0.0.1/ubuntu-11.04-server-amd64.txt
Edit the /tftpboot/pxelinux.cfg/default file and append
url=http://10.66.0.1/ubuntu-11.04-server-amd64.txt
to the end of the append line of our Ubuntu section, so it now looks like:
LABEL ubuntu-11.04-server-amd64-install
MENU LABEL Ubuntu 11.04 Server AMD64 Install
kernel ubuntu-11.04-server-amd64/linux
append vga=788 initrd=ubuntu-11.04-server-amd64/initrd.gz url=http://10.66.0.1/ubuntu-11.04-server-amd64.txt
Try PXE booting the Ubuntu install again. You'll still get some initial questions about language and keyboard (we can deal with those in another post), but you shouldn't be asked about mirrors - the installer will know to pull files from your local webserver.
Go through the install on the client, watch the /var/log/nginx-access.log
file on the server, you'll see the installer fetching all kinds of files,
so you'll know it's all working.
So at this point you've got yourself a working PXE installation environment
and can do a basic Ubuntu server install.
By adding a few more parameters to your seed file and the PXE configuration you can eliminate some of the installer questions. I'll probably write about that in another post, but if you want to figure it out yourself, check out the Ubuntu Installation Guide - Appendix B. Automating the installation using preseeding
There's so many things you can do with the PXE menus, kernel options, and so
on - it can't all be covered in one place. But hopefully you've got a good
starting point now, if you know all the basic services are in place and
working.