December 23, 2006

Bonus entry: Squid to cache Ubuntu net installs

I install enough boxes at home that I’ve got my network set up to serve PXE clients. So I plug in a new box, set it to boot from the network, and I choose from a menu (I made) of choices to install things like CentOS 4 and Ubuntu 6.06.

With RH and derivatives, you can basically copy all the CDs you need into a directory, serve that directory up somewhere (I’ve got a web server running on the file server for this), and do the install entirely off your local network. I was completely unsuccessful trying this with Debian testing (Etch). I was basically successful in doing this with Ubuntu server 6.06, but it was a little funny: I had to tell the installer not to look to security.ubuntu.org for updates when installing, and I had to rewrite my sources.list once I was done (it pointed at my local mirror; these boxes are going out to clients, generally, where they can’t get at my local mirror).

So instead I decided to give an actual network install from the Internet a try. However, I still don’t want to download the same packages over and over again for every install; that’s wasteful of time and bandwidth. I could have used something like apt-proxy, but I wasn’t excited about getting that working on one of my CentOS or Fedora machines. Instead, I decided to try Squid.

Below are the important bits from squid.conf. I’m using Squid as packaged with CentOS 4.4, which is something like Squid version “2.5.” (I hope that “version” exists; I see more about 2.4 or 3.0… gcc 2.96 anyone?) Note that these were mostly derived from a couple pages I read: “Using Squid as an APT cache” and “apt-proxy – a staggering failure.”

# Cache packages up to 200MB in size.
maximum_object_size 200000 KB

# "LFUDA" is "least frequently used with dynamic aging."  It
# supposedly improves byte hit rate at the cost of request hit rate.
# Byte hit rate is more what we want; plus it makes sense to purge the
# least frequently used files first, I think.
cache_replacement_policy heap LFUDA

# Cache directory size defaults to 100MB, which probably isn't big
# enough.  I increased the size to 1GB (which might be overkill,
# depending on what I use Squid for).
cache_dir ufs /var/spool/squid 1000 16 256

# The last three lines are defaults, the rest are mine.
# Keep debs and rpms around for one year.  Technically, I think you
# might end up with objects hanging around only for the duration given
# by "object age" (now - last modified) unless that's more than a year
# See http://tinyurl.com/y2g2c3 (Squid FAQ, Inner Workings, how does
# Squid decide when to refresh a cached object).
refresh_pattern .(u?deb|rpm)$  525949  100%    525949
# Debian package lists expire after a day.
refresh_pattern (^|/)Packages.gz$ 1440  100%    1440
# The following rules are Squid defaults.
refresh_pattern ^ftp:           1440    20%     10080
refresh_pattern ^gopher:        1440    0%      1440
refresh_pattern .               0       20%     4320

In my testing thus far this has worked out great. I’ve even got a preseed file specified in my PXELINUX configuration that sets the proxy automatically. Now I just hope I do another Ubuntu net install within a year, so I can use all those cached files I have.

dhcrelay over GRE tunnels and rebuilding an Ubuntu package

I’ve got a situation such as:

GRE/dhcrelay network diagram

(Diagram made with xfig.) “R&R” stands for “Reynolds and Reynolds,” one of the biggest (if not the biggest) maker of software for automobile dealerships. A dealership gets a R&R server, terminals, printers, that sort of thing. The system seems to handle… everything. There are modules for all manner of things, and it talks back to the OEM’s (i.e., GM, Chrysler, Honda, etc.) systems as well.

R&R have been around for a while, and every dealership my company has done work for has been using R&R for quite a while. As a result, our clients have generally had relatively old R&R equipment, including many serial terminals (or newer PCs with serial connections and emulator software) and serial printers. When R&R goes to upgrade these dealerships—newer server hardware, newer software (I think), and Ethernet connectivity back to the server—some of the older equipment (particularly printers) stick around. To support the older serial equipment when everything else moves to Ethernet and TCP/IP, they plug the serial devices into Digi EtherLite boxes which are basically terminal servers as far as I can tell.

Now, when I say “dealership,” in the case of all our clients I mean several dealerships that share a common owner. Some of the dealerships are physically located adjacent to each other, connected via 100baseFX; others are in different counties. In every case, though, the dealerships have shared a central R&R server. It seems that R&R did the initial work to network the different locations together. They seem to get point-to-point lines run between the different locations, like DDS 56k lines or T-1s. Then they stick a Cisco router on each end and somehow configure the Ciscos to essentially create a bridged network: all locations are in the same broadcast domain, no routing involved. They’ve all ended up using these point-to-point lines to share a single Internet connection between all locations. They allocate IPs for all locations out of a single block of IPs, for any equipment that needs IPs (mainly PCs, to get on the Internet, I think). Yes, this creates management hassles, particularly two locations allocating the same IP.

So we come in, and we end up giving each location its own Internet connection, putting in Linux boxes in place of the Ciscos, and using a VPN to network the sites together. We route them because, well, that just seems to be the sane way to do it. R&R comes in and upgrades everything, puts in a new server, converts almost everything over to Ethernet, and puts the remaining serial devices on one or more EtherLites. Most (all?) dumb terminals get converted to PCs and now speak TCP/IP to the server over Ethernet.

Now, though, you’ve got some EtherLites at remote offices, to talk to the old serial equipment. And, so R&R tells us, the EtherLites have to use bootp to configure themselves. The R&R server has a bootp server running on it, and I’m guessing a TFTP server as well for the EtherLite to pull down its configuration files. I’ve had at least two R&R techs come in, put an EtherLite at a remote office, and then call me saying, “I can’t get my EtherLites to boot from the server, what did you break?” (“Well, uh, they’re routed now, and you didn’t tell me anything about this requirement, so…”) Cisco routers have something like an ip helper-address command that makes them forward bootp/DHCP on to a server you specify. On Linux, you’ve got several choices; the one that I ran into first on my original quest to fix this problem was dhcrelay, part of ISC dhcpd. dhcrelay is found in (at least) RH and Ubuntu.

I’d like to say this setup isn’t difficult, but honestly it kind of is. First of all, our Linux firewalls are generally DHCP servers as well, using ISC dhcpd. You need to tell them not to serve to the EtherLite’s addresses, and make sure they’re not “authoritative,” meaning they can only respond affirmatively to a request and never negatively (since another bootp/DHCP server might respond, like the R&R server). When I did deny bootp in the dhcpd configuration it was still responding to the EtherLites. So here’s a snippet of the configuration I ended up with:

class "reyrey-devices"
{
    match pick-first-value (option dhcp-client-identifier, hardware);
}

subclass "reyrey-devices" 1:0:80:ba:12:34:56;
subclass "reyrey-devices" 1:0:80:ba:12:34:57;
subclass "reyrey-devices" 1:0:80:ba:12:34:58;
# ... and so on for each EtherLite's MAC.  That first "1" octet
# indicates it's an Ethernet MAC, I believe.

subnet 10.123.45.0 netmask 255.255.255.0
{
    pool
    {
        deny members of "reyrey-devices";
        # And all my other DHCP configuration, like range,
        # DHCP options, and fixed addresses go under here too.
    }
}

That’s huge and ugly and complex, but it does work. Perhaps using some other DHCP server would make this easier.

Before I go on to how to configure dhcrelay, which is actually rather easy compared to the time it took me to figure out the about dhcpd configuration, let me mention how the VPN works. It’s an IPsec VPN, but using GRE over IPsec for greater flexibility and security.

Configuring dhcrelay is basically done through the command line. On Ubuntu, you modify the command line options for dhcrelay in something like /etc/defaults/dhcp3-relay, and it’s something like /etc/sysconfig/dhcrelay in RH. You have to tell dhcrelay a device to listen on for DHCP requests, and you have to tell it the IP of a server to forward the request to. When a device (i.e., an EtherLite) DHCPs, that is a broadcast. When dhcrelay forwards, however, it’s a unicast to the specified DHCP server. Thus, I didn’t think it would need to be told which interfaces it should listen on for the unicast reply from the DHCP server. Wrong: if you don’t tell it both the LAN interface, and the interface it reaches the DHCP server on (the GRE tunnel interface, in my case), it doesn’t work.

Now, if that wasn’t enough, on Linux every binary dhcrelay I’ve seen has used LPF (Linux packet filter). It took me a while, but I eventually decided that LPF doesn’t work right on a GRE tunnel, and I don’t exactly know why. You even get a warning about an unknown interface type from dhcrelay, though it looks like it runs anyway; runs, but doesn’t work. The solution? Recompile dhcrelay with USE_SOCKETS.


Which is my cue to document, by way of example, how to rebuild a package under Ubuntu. I’m going to rebuild the dhcp3 package in Ubuntu, which provide ISC’s dhcpd, dhclient, and dhcrelay. I got the basic idea for how to do this from an article entitled “Rebuilding Debian packages.”

First, to make sure you’ve got everything you need to build packages, I suggest installing: dpkg-dev gcc libc-dev dpatch fakeroot devscripts ccache debhelper. Don’t forget to put /usr/lib/ccache in your path before /usr/bin so you get ccached versions of all the binaries.

Next, run apt-get build-dep dhcp3 which will install everything you need to rebuild the dhcp3 package. Then:

[darkness@gateway-honda dpkg-build]$ apt-get source dhcp3
Reading package lists... Done
Building dependency tree... Done
Skipping already downloaded file 'dhcp3_3.0.3-6ubuntu7.dsc'
Skipping already downloaded file 'dhcp3_3.0.3.orig.tar.gz'
Skipping already downloaded file 'dhcp3_3.0.3-6ubuntu7.diff.gz'
Need to get 0B of source archives.
dpkg-source: extracting dhcp3 in dhcp3-3.0.3
dpkg-source: unpacking dhcp3_3.0.3.orig.tar.gz
dpkg-source: applying ./dhcp3_3.0.3-6ubuntu7.diff.gz
[darkness@gateway-honda dpkg-build]$ ls -l
total 936
drwxr-x--- 15 darkness users   4096 2006-12-23 04:09 dhcp3-3.0.3
-rw-r-----  1 darkness users  66377 2006-05-05 10:08 dhcp3_3.0.3-6ubuntu7.diff.gz
-rw-r-----  1 darkness users    777 2006-05-05 10:08 dhcp3_3.0.3-6ubuntu7.dsc
-rw-r-----  1 darkness users 870240 2005-11-14 07:40 dhcp3_3.0.3.orig.tar.gz

Sorry, I did this apt-get source once before, which is why I had them downloaded already. Now we’ll make a patch against the DHCP sources that defines USE_SOCKETS. dhcp3 happens to use software called dpatch to manage applying a series of patches, so we’ll use the utilities provided by dpatch to make our new patch.

[darkness@gateway-honda dpkg-build]$ cd dhcp3-3.0.3
[darkness@gateway-honda dhcp3-3.0.3]$ cat debian/patches/00list
Makefile
common
ddns-update-style-default
dhclient-script-exit-status
dhcpd-chdir
documentation
remove-excessive-junk
site.conf
add-libdst.a-dhcp3-dev
ignore-invalid-interfaces
droppriv
deroot-client
deroot-server
revert-next-server
dhcpd.conf-subnet-examples
[darkness@gateway-honda dhcp3-3.0.3]$ dpatch-edit-patch use-bsd-sockets 
                      dhcpd.conf-subnet-examples
dpatch-edit-patch: * /home/darkness/dpkg-build/dhcp3-3.0.3/debian/patches/use-bsd-sockets.dpatch
does not exist, it will be created as a new dpatch.
#
# ... then it does a whole bunch of stuff, after which:
#
dpatch-edit-patch:

Now launching an interactive shell in your work directory. Edit your files.
When you are done, exit the shell. When you exit the shell, your patch will be
automatically updated based on the changes in your work directory.

If you wish to abort the process, exit the shell such that it returns an exit
code of "230". This is typically done by exiting the shell with the command
'exit 230'.
sh-3.1$ pwd
/tmp/dpep-work.JzHxjx/dhcp3-3.0.3

(I’ll be wrapping text where I feel appropriate.) dpatch-edit-patch has, I gather, made a copy of the dhcp3 source, applied all patches up to and including the dhcpd.conf-subnet-examples patch I specified last on the command line, started a new shell in this temporary copy of the source, and now wants me to modify this copy of the source.

sh-3.1$ ed includes/site.h
5837
/USE_SOCKETS/d
i
#define USE_SOCKETS
.
wq
5831
sh-3.1$ exit
exit
dpatch-edit-patch: * Creating new patch /home/darkness/dpkg-build/dhcp3-3.0.3/debian/patches/use-bsd-sockets.dpatch
dpatch-edit-patch: Warning: debian/patches/00template not exist, using hardcoded default.
dpatch-edit-patch: /home/darkness/dpkg-build/dhcp3-3.0.3/debian/patches/use-bsd-sockets.dpatch created.
[darkness@gateway-honda dhcp3-3.0.3]$ cat debian/patches/use-bsd-sockets.dpatch
#! /bin/sh /usr/share/dpatch/dpatch-run
## use-bsd-sockets.dpatch by  <darkness@gateway-honda>
##
## All lines beginning with `## DP:' are a description of the patch.
## DP: No description.

@DPATCH@
diff -urNad dhcp3-3.0.3~/includes/site.h dhcp3-3.0.3/includes/site.h
--- dhcp3-3.0.3~/includes/site.h        2002-03-12 13:33:39.000000000 -0500
+++ dhcp3-3.0.3/includes/site.h 2006-12-23 04:19:05.000000000 -0500
@@ -135,7 +135,7 @@
the aforementioned problems do not matter to you, or if no other
API is supported for your system, you may want to go with it. */

-/* #define USE_SOCKETS */
+#define USE_SOCKETS

 /* Define this to use the Sun Streams NIT API.

(I’m just showing off using ed.) So what I’ve done is uncommented #define USE_SOCKETS, then exited the subshell dpatch-edit-patch put me in. When I did this, dpatch did a diff between the original source tree I started in and the temporary source tree where I modified some things. It saved this diff as the patch name I specified on the dpatch-edit-patch command line.

[darkness@gateway-honda dhcp3-3.0.3]$ dpatch list-all | grep bsd
[darkness@gateway-honda dhcp3-3.0.3]$ echo 'use-bsd-sockets' >>debian/patches/00list
[darkness@gateway-honda dhcp3-3.0.3]$ dpatch list-all | grep bsd
use-bsd-sockets

dpatch list-all will show you all patches dpatch will apply when it builds this package. You’ll notice our patch wasn’t going to be applied until we appended it to debian/patches/00list.

Next we want to change the version number, so it’s clear we modified this package locally. Apparently the package version is taken from the latest (top-most) entry in debian/changelog. dch is in the devscripts package that we installed above. Run dch -i and dch will insert a new skeleton entry in changelog with an incremented version number, then pop you into $EDITOR to edit the entry. Here’s my (slightly obfuscated) entry:

(3.0.3-6ubuntu7+codefu.1) dapper; urgency=low
   * Forced use of BSD sockets, to make dhcrelay work right over a GRE
     tunnel.

 -- My Name <my@email.address>  Sat, 23 Dec 2006 04:27:54 -0500

Now, building the package is easy: debuild -e PATH -uc -us. The first option tells debuild not to “sanitize” the PATH environment variable; doing so removes /usr/lib/ccache from the PATH, and I like ccache (when you screw up on some long running package build and you need to restart the whole thing over, you’ll like ccache too). Just make sure there’s nothing funny in your PATH that might affect whatever package you’re building, lest your build not be repeatable on a different machine. The remaining options tell debuild not to try to sign anything.

When this finishes, hopefully successfully, you’ll end up with fixed dhcp3* packages in the parent directory. Install them with dpkg -i, and then configure APT not to try to upgrade your new packages. Now start up dhcrelay, telling it to listen on both the GRE tunnel as well as on the LAN, and suddenly the EtherLites can boot. Hooray.

One note: if you wish to “downgrade” to the Ubuntu packages for DHCP for some reason (maybe you’ve gotten rid of all your EtherLites and other weird equipment; good for you), you can do it with something like apt-get install dhcp3-{common,server,client,relay}/dapper. The /dapper at the end apparently tells APT what distribution to look in, and it’ll gladly downgrade you (and explicitly tell you that it is downgrading) at that point. Note that, if the package you want to downgrade has had an update released for it, you might need to specify /dapper-updates instead; or just apt-get upgrade after downgrading to the original release version, which should grab any available updates for you.

December 14, 2006

Making Apache authenticate against AD

Setting up mod_auth_ldap to talk to Active Directory should be pretty easy. Except it isn’t.

Up front: I’m doing this on CentOS 4.4, Apache 2.0.52 (httpd-2.0.52-28.ent.centos4), OpenLDAP 2.2.13 (openldap-2.2.13-6.4E).

First and foremost, AD LDAP doesn’t allow anonymous binds, so Apache needs a user and password to bind with. This user and password will be used to search AD for the user name entered by the user. On my small and hopefully “mostly default” AD domain, I was able to use just a run-of-the-mill user account for this purpose.

Now you’ll need the DN of that user. I had no idea what that was (though I can now make a pretty good guess; see below) since all the default GUI administration tools seem to hide details like the DN or RDN of an object. So to find the DN for the user you just created, I’ve come up with two options.

  1. Use ldifde. This was available on my Windows Server 2003 controller without having to install anything special, as far as I know. Say I created a user named mod_auth_ldap (which I did). To find the DN:

    J:>ldifde -r samaccountname=mod_auth_ldap -l dn -f foo.out
    Connecting to "your-ad-server.ad.yourdomain.com"
    Logging in as current user using SSPI
    Exporting directory to file foo.out
    Searching for entries...
    Writing out entries.
    1 entries exported
    
    
    The command has completed successfully
    
    
    J:>type foo.out
    
    
    dn: CN=Apache mod_auth_ldap,OU=Some Users,DC=ad,DC=yourdomain,DC=com
    changetype: add
    

    There is a -d option to ldifde that specifies the base DN for the search. Of course, I didn’t know the base DN to start with; by leaving it out, I guess ldifde picked the root of the AD domain the computer was a member of. There are other options, to specify things like the server and credentials; similarly, by not specifying these I assume they default to one of the domain’s LDAP servers (or perhaps it defaults to localhost, since I was on one of the LDAP servers) and your current user credentials, respectively.

  2. Go find “ADSI Edit” for your version of Windows. This appears to be a MMC snap-in. It lets you browse the AD LDAP tree as, well, LDAP: I can see my domain’s base DN, and I can graphically browse the DIT. If you have permission, you can even edit things (not recommended; just because it looks like LDAP doesn’t mean it’s not Microsoft LDAP). This looks like a very handy tool to have available for debugging when you’re trying to connect some external LDAP application to AD LDAP. If you can get it installed, I recommend it.

    ADSI Edit (adsiedit.msc, adsiedit.dll, others perhaps) can be found in the Windows Server 2003 Support Tools. It may also be available in the “Administrators Tools Pack” (“adminpak”), though it wasn’t there in the Administrators Tools Pack for Windows 2003. Hopefully ADSI Edit will run (or has versions that will run) under, say, XP.

Looking at the usual “Active Directory Users and Computers” tool, I can see how I could have guessed dc=ad,dc=foo,dc=com from the domain at the root of the tree, and I can see the icons that seem to indicate “organizationalUnit” as the RDN versus the icons that seem to indicate “commonName” (or at least “not organizationalUnit”) for the RDN. I also know now that commonName is the default RDN for a user. So I can now make a decent guess of a user’s DN. (Why should I have to, again?)

In any case, I do recommend testing your new Apache LDAP user with ldapsearch (part of OpenLDAP, and probably a dozen other LDAP kits):

[darkness@gateway ~]$ ldapsearch -x -W 
    -H ldap://server1.ad.yourdomain.com -b dc=ad,dc=yourdomain,dc=com 
    -D "cn=Apache mod_auth_ldap,ou=Some Users,dc=ad,dc=yourdomain,dc=com" 
    'samAccountName=mod_auth_ldap' dn
Enter LDAP Password:
# extended LDIF
#
# LDAPv3
# base <dc=ad,dc=yourdomain,dc=com> with scope sub
# filter: samAccountName=mod_auth_ldap
# requesting: dn
#

# Apache mod_auth_ldap, Some Users, ad.yourdomain.com
dn: CN=Apache mod_auth_ldap,OU=Some Users,DC=ad,DC=yourdomain,DC=com

# search reference
ref: ldap://ForestDnsZones.ad.yourdomain.com/DC=ForestDnsZones,DC=ad,DC=yourdomain,DC=com

# search reference
ref: ldap://DomainDnsZones.ad.yourdomain.com/DC=DomainDnsZones,DC=ad,DC=yourdomain,DC=com

# search reference
ref: ldap://ad.yourdomain.com/CN=Configuration,DC=ad,DC=yourdomain,DC=com

# search result
search: 2
result: 0 Success

# numResponses: 5
# numEntries: 1
# numReferences: 3

Gosh, I wonder what those search reference things are? Oh well, we’ll just ignore them… (Yeah, you quite possibly can’t; keep reading.)

OK, now to configure Apache. I based my configuration on this article on how to have Apache authenticate against AD. Here’s my configuration:

<Location /woc>
        AuthLDAPAuthoritative on
        AuthType Basic
        AuthName "My web application"
        AuthLDAPBindDN "cn=Apache mod_auth_ldap, ou=Some Users, dc=ad, dc=yourdomain, dc=com"
        AuthLDAPBindPassword "sekret"
        AuthLDAPURL "ldap://server1.ad.yourdomain.com/dc=ad,dc=yourdomain,dc=com?samAccountName?sub?(objectClass=*)"
        #require valid-user  # you can use this if you want any user
        require group CN=Web app users,OU=Some Groups,DC=ad,DC=yourdomain,DC=com
</Location>

A few notes. First, I’m using regular LDAP, not LDAP with Start TLS or LDAP over SSL (LDAPS). According to “How to enable LDAP over SSL with a third-party certification authority” Windows (2000, at least, and I bet 2003 too) doesn’t support Start TLS. LDAPS is not enabled by default unless you… do some stuff. I haven’t done that, so my passwords are being passed in the clear. You have been warned.

Next, note there are no quotes around the group. This is according to the documentation:

This directive specifies an LDAP group whose members are allowed access. It takes the distinguished name of the LDAP group. Note: Do not surround the group name with quotes.

So you’ve put the Apache configuration in. You reload the configuration, and go to test it with some user in the Web app users group. And… it fails. Maybe you get an error in Apache’s error log such as:

[Wed Dec 13 23:34:41 2006] [warn] [client 127.0.0.1] [7122]
auth_ldap authenticate: user mod_auth_ldap authentication failed;
URI /woc [ldap_search_ext_s() for user failed][Operations error]

Let me save you some time and point you to Apache bug # 26538.

Essentially the problem seems to be that you’re searching at the root of the AD tree, and when you do that (on the normal LDAP port, at least) AD returns some referrals. OpenLDAP will, by default, then proceed to attempt to follow those referrals… with anonymous binds. AD denies the searches from anonymously-binded connections, and mod_auth_ldap chokes and refuses to go any further. I tried using the “global catalog” (ldap://server1.ad.yourdomain.com:3268) and it kind of worked, except it seemed that I wasn’t able to see the group I created (Web app users). In retrospect I might have been searching wrong, so it might be fine to use the “global catalog.” What I ended up doing is turning off referrals system-wide by putting REFERRALS off in /etc/openldap/ldap.conf (on CentOS 4). Despite the fact that this option is undocumented in ldap.conf(5), it worked to fix the error.

Of course, then I got another error saying my user wasn’t in the group I had created. The problem: I tried to get smart and set Web app users as the user’s primary group. My guess is that a group object doesn’t list a particular user in a member attribute if the group is the user’s primary group. In other words, if I had left Domain users as the user’s primary group, then added Web app users, it would have been fine. The solution: … don’t make Web app users any user’s primary group.

Thus do I finally have Apache authenticating against AD. Reminder: use HTTPS, lest you pass your AD passwords in the clear. (And get that LDAPS working.)