Sunday, July 10, 2011

How To Set Up And Integrate An Ubuntu 10.04 LTSPv5 Server Into A Windows 2008 Active Directory Domain

Introduction

 
With the current economic conditions, companies have been trying to do more with less by recycling old hardware and utilizing open source software. That's also true where I work. Between our call center, finance, distribution, warehouse, and sales departments over 50% of the company's employees are running on RHEL4 LTSP thin clients. Recently it was decided to update our LTSP environment from RHEL4 to Ubuntu 10.04 with LTSPv5. Using the following steps, I was able to setup and integrate an Ubuntu 10.04 LTSPv5 server into a Windows 2008 Active Directory domain.

Assumptions

It is assumed that you have a Windows 2008 Active Directory Domain setup and working properly along with a DHCP server. Your domain controller can be your DHCP server or you can setup a different box to distribute the DHCP leases. If your domain controller or DHCP server are not setup, please set these up first. It is also assumed that the reader has some basic Linux experience. You will need to know how to move around in the Linux terminal, install applications, and edit files using vi or nano.

Hardware

Ubuntu LTSP Server

Dell PowerEdge 2650
2 Dual Core Intel(R) Xeon(TM) CPU 1.80GHz CPU's
8 GB RAM
3 73 GB SCSI U320 10K Hard drives in RAID 5 configuration
Dual power supply

Thin Clients

Dell Optiplex GX260
1 GB RAM
On-board video
No HD

Network Layout

For the purpose of this tutorial, this is the layout of the domain.internal network on the 10.0.0.0/24 subnet.
Network Layout
dc.domain.internal
Windows 2008 Server running Active Directory and DNS
server.domain.internal
Windows 2003 Server hosting user home directories and file shares
thinserver.domain.internal
Ubuntu 10.04.2 server with LTSPv5
dhcp.domain.internal
CentOS 5.0 server running dhcpd

Ubuntu LTSP Installation

To install Ubuntu with LTSP, follow the instructions here.
If your server has more than 4 GB of RAM, make sure you install the Linux PAE Kernel.
sudo apt-get install linux-generic-pae linux-headers-generic-pae

Add thinserver to the Windows Domain

Before we add thinserver to the domain, we're going to have to install Samba along with some other packages.
sudo apt-get install samba smbclient winbind libpam-cracklib krb5-user
Make sure that thinserver is named correctly.
hostname
If the hostname command doesn't return thinserver.domain.internal, rename it to thinserver.domain.internal.
hostname thinserver.domain.internal
Edit the /etc/resolv.conf to use dc.domain.internal as the primary DNS server.
search domain.internal
nameserver 10.0.0.10
On your domain controller create a host (A) record in your DNS for thinserver.
Verify that thinserver can resolve domain.internal:
nslookup domain.internal
The results should look something like this:
test@thinserver:~$ nslookup domain.internal
Server:        10.0.0.10
Address:    10.0.0.10

Name:    domain.internal
Address: 10.0.0.10
Make sure that Samba and Winbind are not running:
/etc/init.d/smbd stop
/etc/init.d/winbind stop
Just to be safe lets backup the smb.conf, krb5.conf, and PAM common files. I like to append the date when I make a backup of a file so that I know when the changes were made.
d=`date "+%m%d%y"`
cp /etc/samba/smb.conf{,.$d}
cp /etc/krb5.conf{,.$d}
mkdir /etc/pam.d/backup
cd /etc/pam.d/
for file in `ls`;do cp $file{,.$d}; done
mv *.$d backup/
Edit the /etc/krb5.conf file to look like this:
[logging]
 default = FILE:/var/log/krb5libs.log
 kdc = FILE:/var/log/krb5kdc.log
 admin_server = FILE:/var/log/kadmind.log

[libdefaults]
 default_realm = DOMAIN.INTERNAL
 default_keytab_name = FILE:/etc/krb5.keytab
 default_tgs_entypes = rc4-hmac des-cbc-md5
 default_tkt_entypes = rc4-hmac des-cbc-md5
 permitted_entypes = rc4-hmac des-cbc-md5
 dns_lookup_realm = true
 dns_lookup_kdc = true
 ticket_lifetime = 24h
 forwardable = yes

[realms]

 DOMAIN.INTERNAL = {
  kdc = DC.DOMAIN.INTERNAL:88
  default_domain = DOMAIN.INTERNAL 
 }

[domain_realm]

 domain.internal = DOMAIN.INTERNAL
 .domain.internal = DOMAIN.INTERNAL
[appdefaults]
 forwardable = true
 pam = {
   minimum_uid = 16777216
   debug = false
   ticket_lifetime = 36000
   renew_lifetime = 36000
   krb4_convert = false
   DOMAIN.INTERNAL = {
 ignore_k5login = true
 }
 }
Edit the /etc/samba/smb.conf file to look like this:
[global]
   workgroup = DOMAIN
   password server = *
   realm = DOMAIN.INTERNAL
   local master = no
   security = ads
   idmap backend = tdb
   idmap uid = 16777216-33554431
   idmap gid = 16777216-33554431
   idmap config DOMAIN : backend = rid
   idmap config DOMAIN : range = 16777216-33554431
   idmap cache time = 60 
   template homedir = /home/%u
   template shell = /bin/bash
   kerberos method = secrets and keytab
   dedicated keytab file = /etc/krb5.keytab
   winbind separator = +
   winbind use default domain = yes
   winbind refresh tickets = true
   winbind cache time = 10
   winbind offline logon = true
   winbind enum users = Yes
   winbind enum groups = Yes
   passdb backend = tdbsam

   server string = Samba Server Version %v
   log file = /var/log/samba/%m.log
   max log size = 50
Moving forward it's advisable to have a second root terminal open just in case something doesn't work as expected. Happens to the best of us :o)
I would recommend creating a "linux_admins" group in Active Directory and adding it to the /etc/sudoers file. An alternative is to add the "domain admins" group and to login using the administrator account.
# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL
%linux_admins ALL=(ALL) ALL
Edit the PAM common file /etc/pam.d/common-account:
account     sufficient   pam_winbind.so use_first_pass cached_login 
account     required      pam_unix.so broken_shadow
Edit the PAM common file /etc/pam.d/common-auth:
auth [success=2 default=ignore] pam_unix.so nullok_secure
auth [success=1 default=ignore] pam_winbind.so krb5_auth krb5_ccache_type=FILE cached_login try_first_pass
auth requisite   pam_deny.so
auth required   pam_permit.so
Edit the PAM common file /etc/pam.d/common-password:
password requisite   pam_cracklib.so retry=3 minlen=8 difok=3
password [success=2 default=ignore] pam_unix.so obscure use_authtok try_first_pass sha512
password [success=1 default=ignore] pam_winbind.so use_authtok try_first_pass
password requisite   pam_deny.so
password required   pam_permit.so
password optional   pam_gnome_keyring.so
Edit the PAM common file /etc/pam.d/common-session:
session     required     pam_env.so
session     required        pam_unix.so
session     required        pam_winbind.so use_first_pass 
session     required        pam_limits.so
session     required        pam_mkhomedir.so
session     [success=1 default=ignore]  pam_succeed_if.so service in crond quiet use_uid
Edit the PAM common file /etc/pam.d/common-session-noninteractive:
session  [default=1]  pam_permit.so
session  requisite  pam_deny.so
session  required  pam_permit.so
session  optional  pam_winbind.so cached_login
session  required  pam_unix.so 
Make sure that /etc/nsswitch.conf has the winbind entries for login.
passwd:         compat winbind
group:          compat winbind
shadow:         compat
Now we're ready to add thinserver to the Windows Domain.
kinit administrator@DOMAIN.INTERNAL
net ads join -U administrator
net ads keytab create -U administrator
/etc/init.d/smbd start
/etc/init.d/winbind start
Verify that you are on the domain and that you can see all the users and groups in the domain.
wbinfo -u
wbinfo -g
getent passwd
You should now be able to log onto the server with your domain username and password. Verify that you're getting a Kerberos ticket.
klist

DHCP Server Settings

 
These are the settings you'll need to make so that a thin client can boot from thinserver.
default-lease-time            21600;
max-lease-time                21600;

option subnet-mask            255.255.255.0;
option broadcast-address      10.255.255.255;
option routers                10.0.0.1;
option domain-name-servers    10.0.0.10
option domain-name            "domain.internal";
option root-path              "/opt/ltsp/i386";

host thinclient1 {
        next-server             10.0.0.10;
        hardware ethernet       00:AA:BB:CC:DD:EE;
        fixed-address           10.0.0.100;
        filename                "ltsp/i386/pxelinux.0";
        option root-path        "10.0.0.10:/opt/ltsp/i386";
}

host thinclient2 {
        next-server             10.0.0.10;
        hardware ethernet       00:BB:CC:DD:EE:FF;
        fixed-address           10.0.0.101;
        filename                "ltsp/i386/pxelinux.0";
        option root-path        "10.0.0.10:/opt/ltsp/i386";
}

Thin Client Setup

To make things easier for my thin client users I installed the XPGnome theme. You can download it from here. The only change that I made to the stock install was that I modified the Start menus. I then installed Adobe Reader 9 and Skype.
I did have a problem with the "Log Out" icon not showing up. To fix it I found an icon on Google that was 48x48 and then used Gimp to scale it down to 32x32, 24x24, 22x22, and 16x16. Rename the icon system-log-out.png and save it to /usr/share/icons/GnomeXP/{icon size}/actions.
The final result looks like this.
Thinclient Desktop
To make these settings the default for all users that login to thinserver, copy from your home folder (or the user's home folder that installed the XPGnome theme) the .config, .gconf, .icons, .local, and .themes folders to /etc/skel. Remember moving forward any changes that you make that you want to apply to everyone will need to be copied over also.

Mounting Windows Shares at Login

There are a couple of ways to do this in Linux but I finally decided on using Bash and Perl scripts in conjunction with Ubuntu's "Startup Applications" to handle the mounting of Windows shares. I will include all scripts in this tutorial so that you can modify them to fit your environment and improve them as you see fit.
Before we continue, make sure that the NETLOGON share from dc.domain.internal is mounted on thinserver.domain.internal. I created a generic domain account that has permissions to only list the contents of the AD. For the sake of this example that account name is "public" with the password of "password".
Create a folder to mount the share to.
sudo mkdir /mnt/logon
Mount the NETLOGON share by adding this entry into your /etc/fstab file.
//dc.domain.internal/netlogon      /mnt/logon           cifs   username=public,password=password 0  0
Mount the share.
sudo mount -a
The scripts used depend on each user having their own login batch file in the NETLOGON share and their own share on server.domain.internal. Here is a batch file for user "John Doe" with username "jdoe". The batch file name is jdoe.bat. You can use just one batch file and hardcode the name into the script.
@echo off
NET USE S: \\server\common
NET USE T: \\server\IT
Create the win_share.sh script and save it to /usr/local/bin/. The win_share.sh script checks to see if the .mount.sh and .umount.sh scripts for the user logging in exist and if they do delete them. It then creates new .mount.sh and .umount.sh scripts by running the /usr/local/bin/mount.pl Perl script. Finally it mounts the users shares by running the .mount.sh script. The user shouldn't get prompted for a password since the script uses Kerberos to authenticate on server.domain.internal.
#!/bin/sh
# Check to see if .mount.sh and .umount.sh exist, if so delete them!

if [ -f /home/$USER/.mount.sh ]; then
   rm /home/$USER/.mount.sh
fi

if [ -f /home/$USER/.umount.sh ]; then
   rm /home/$USER/.umount.sh
fi

# Create the .mount.sh and .umount.sh scripts from users batch file

/usr/local/bin/mount.pl $USER

# Mount network shares when logging on.
/home/$USER/.mount.sh
Create the mount.pl script in /usr/local/bin/.
#!/usr/bin/perl
# Build dynamic ~user/.mount.sh based on logon.bat

$user = $ARGV[0];
$file = "/mnt/logonbat/$user.bat";  # <-- Change this from $user to the name of the batch script if you only use one.

die if ! $user;
die if ! -e $file;

open (PAM_CONF, ">/home/$user/.mount.sh");
open (LOGOFF, ">/home/$user/.umount.sh");

print PAM_CONF qq{#!/bin/sh
if [ ! -d /home/$user/Home ]; then
mkdir /home/$user/Home
fi
mount.cifs //server/$user /home/$user/Home -o username=$user,sec=krb5
};

print LOGOFF qq{#!/bin/sh
if [ "`cat /proc/mounts | grep /home/$user/Home | wc -l`" -ge "1" ]; then 
umount.cifs /home/$user/Home 
fi \n};

my(@arr)=`cat /mnt/logonbat/$user.bat`;
$mounts = parse_batfile(\@arr);
foreach $mount (@$mounts) {
  chomp($mount);
  ($server,$share) = $mount =~ /\\\\(.*)\\(.*)/;
  $share =~ tr/\cM//d;
  $mnt = $share;

  # skip AUDIT.  It's for PCs only
  next if $mnt =~ /AUDIT/;

  # skip personal shares.  
  next if lc("$mnt") eq lc("$user");
  next if ! $mnt;

  #strip dollar sign from mount point
  $mnt =~ s/\$$//;

  # make sure mount point is unique
  $mnt .= "-$server"  if $seen{$mnt}++;

  # upshift first letter of mnt point
  $mnt =~ s/^(.)(.*)/\u$1$2/g;

#  print PAM_CONF "volume $user cifs $server $share  /home/$user/$mnt  - - -\n";
  print PAM_CONF qq{if [ ! -d /home/$user/$mnt ]; then
mkdir /home/$user/$mnt
fi
mount.cifs //$server/$mnt /home/$user/$mnt -o username=$user,sec=krb5 \n};

  print LOGOFF qq{if [ "`cat /proc/mounts | grep /home/$user/$mnt | wc -l`" -ge "1" ]; then 
umount.cifs /home/$user/$mnt 
fi \n};
}

close PAM_CONF;
close LOGOFF;
system ("chown $user:16777729 /home/$user/.mount.sh");   #  16777729 is my GID for "Domain Users"
system ("chown $user:16777729 /home/$user/.umount.sh");  #  16777729 is my GID for "Domain Users"
system ("chmod +x /home/$user/.mount.sh");
system ("chmod +x /home/$user/.umount.sh");

# All done

sub parse_batfile {
  my($file) = @_;
  my(@mounts);
  foreach $line (@$file) {
    (@val) = split / /,$line;
    if (uc($val[0]) eq "NET" && uc($val[1]) eq "USE") {
       push (@mounts,$val[3]);
    }
    if ($val[0] eq "CALL") {
      my($match) = $val[1]  =~ /\\\\.*\\NETLOGON\\(.*)/ ;
      if ($match) {
        chop($match);
        my(@arr)=`cat /mnt/logonbat/$match`;
        $mounts = parse_batfile(\@arr);
        unshift @mounts, @$mounts;
      }
    }
  }
  return \@mounts;
}
This is what the .mount.sh script looks like for jdoe.
#!/bin/sh
if [ ! -d /home/jdoe/Home ]; then
mkdir /home/jdoe/Home
fi
mount.cifs //server/jdoe /home/jdoe/Home -o username=jdoe,sec=krb5
if [ ! -d /home/jdoe/common ]; then
mkdir /home/jdoe/common
fi
mount.cifs //server/common /home/jdoe/common -o username=jdoe,sec=krb5 
if [ ! -d /home/jdoe/IT ]; then
mkdir /home/jdoe/IT
fi
mount.cifs //server/IT /home/jdoe/IT -o username=jdoe,sec=krb5 
Once you have win_share.sh and mount.pl scripts in place, create the "Startup Application" to run it at login. To create the "Startup Application" go to "Preferences/Startup Applications".
Startup Applications

Removing Windows Shares at Log off

To remove the Windows shares that were mounted at login I used pam_script.so. Pam_script is a PAM module that among other things will allow you to run scripts at session login and logoff. The reason why I didn't use pam_script for the login is because it runs as root and the win_share.sh and mount.pl scripts depend on the $USER variable. Download the libpam-script package from here.
Install libpam-script:
sudo dpkg -i libpam-script_1.1.4-1_i386.deb
This is the .umount.sh script that was created from the mount.pl Perl script for the user jdoe.
#!/bin/sh
if [ "`cat /proc/mounts | grep /home/jdoe/Home | wc -l`" -ge "1" ]; then 
umount.cifs /home/jdoe/Home 
fi 
if [ "`cat /proc/mounts | grep /home/jdoe/common | wc -l`" -ge "1" ]; then 
umount.cifs /home/jdoe/common 
fi 
if [ "`cat /proc/mounts | grep /home/jdoe/IT | wc -l`" -ge "1" ]; then 
umount.cifs /home/jdoe/IT 
fi 
Create and edit the /usr/share/libpam-script/pam_script_ses_close file.
#!/bin/sh
# pam_script_ses_close script to remove windows shares

/home/$PAM_USER/.umount.sh 2>&1 >> /var/log/umount.log
Add this line to /etc/pam.d/common-session:
session     optional        pam_script.so
Test to make sure that it's working as expected.

Passwordless SSH with Kerberos

One of the benefits of having a Kerberos enabled server is that you can now enable passwordless login via SSH. To make this work you need to have both your Linux workstation and server on the domain with Kerberos configured correctly.
Make these changes in the /etc/ssh/sshd_config file on thinserver:
# GSSAPI options
GSSAPIAuthentication yes
GSSAPICleanupCredentials yes
UseDNS yes
Make these changes in the /etc/ssh/ssh_config file on your Linux workstation:
GSSAPIAuthentication yes
GSSAPIDelegateCredentials yes
Test ssh to make sure that authentication is working with Kerberos. Try to login to thinserver from your workstation.
ssh -v thinserver
If authentication with Kerberos succeeded you shouldn't be promted for a password and you should see the "debug1: Authentication Succeeded (gssapi-with-mic) message:
debug1: Next authentication method: gssapi-keyex
debug1: No valid Key exchange context
debug1: Next authentication method: gssapi-with-mic
debug1: Delegating credentials
debug1: Delegating credentials
debug1: Authentication succeeded (gssapi-with-mic).

Problems and Solutions

These are some of the other issues that I came accross. Hopefully they'll help someone.
PROBLEM : LTSP client authenticates but logs out immediately
SOLUTION: gconftool-2 --direct --config-source xml:readwrite:/etc/gconf/gconf.xml.mandatory --type string --set /desktop/gnome/session/required_components/windowmanager metacity
PROBLEM : No VNC on thin clients
SOLUTION: http://bootpolish.net/home_ltsp_installx11vnconltsp5
PROBLEM : Change the default login screen to a custom one
SOLUTION: https://help.ubuntu.com/community/EdubuntuFAQ
PROBLEM : How to setup root password on thin client
SOLUTION: https://help.ubuntu.com/community/EdubuntuFAQ
PROBLEM : No logout icon
SOLUTION: http://ubuntuforums.org/archive/index.php/t-815188.html

4 comments:

  1. Sameh,

    Thanks for posting my article. However you forgot to credit the author.

    Best Regards,

    Paul R. Blaha

    ReplyDelete
  2. The post's title URL is pointing to your original article, is this enough?

    ReplyDelete
  3. Sameh,

    Yes. Thank you.

    Best Regards,

    Paul R. Blaha

    ReplyDelete
  4. Thanks for posting great infomation about security synapse.

    Thin Client Hardware & RDP Thin Client

    ReplyDelete