This recipe demonstrates a basic framework that can be used to centrally manage one or more root crontabs using WebJob. Payload and Delivery (PaD) scripts will be used to package crontabs as executables -- thus allowing WebJob to download and execute them. The motivation behind this recipe is that it's desirable to have a secure and efficient means of managing crontabs from a central location. The level of secureness, however, depends on your ability to harden the WebJob server and implement the basic concepts of this recipe in a secure fashion. The focus of this recipe will be on establishing, not securing, such a framework. Cooking with this recipe requires that you already have an operational WebJob server. If this is not the case, refer to the instructions provided in the INSTALL file that comes with the source distribution. While this recipe was written specifically for FreeBSD systems, the concepts should be portable. Under FreeBSD there are two root-level crontabs: /etc/crontab and /var/cron/tabs/root. Respectively, these serve as the primary and secondary root-level crontabs. Our goal, then, will be to use the primary crontab to manage the secondary crontab. We do this for two reasons: 1) we want /etc/crontab to remain relatively static and 2) we would like have a fail-safe mechanism that allows us to automatically and remotely recover should the managed crontab become corrupted or inaccessible. This recipe assumes that you wish to setup one master crontab and have it replicated to one or more client systems. As such, the following steps will instruct you to place the master crontab in a common tree and create symbolic links to it for each client that is to be managed. 1. Create a common tree containing two directories, crontabs and commands, on your WebJob server. The first directory will host generic crontab files, and the second is where the corresponding PaD executables will reside. mkdir -p -m 755 /integrity/profiles/common/crontabs mkdir -p -m 755 /integrity/profiles/common/commands 2. Create a crontab containing the job shown below. Put this file in the crontabs directory and call it crontab.root. Don't use the crontab program to create this file -- just use a regular editor or the command provided below. Set the permissions on this file to suit your security needs. --- crontab.root --- 0 1 * * * echo "This cron job was executed on `hostname` at `date`" --- crontab.root --- sed -e '1,/^--- crontab.root ---$/d; /^--- crontab.root ---$/,$d' webjob-pad-crontab.txt > crontab.root install -m 644 -o root -g wheel crontab.root /integrity/profiles/common/crontabs 3. Next, create crontab.root.pad and put it in the commands directory. pad-make-script --create crontab.root > crontab.root.pad install -m 644 -o root -g wheel crontab.root.pad /integrity/profiles/common/commands 4. To manage this crontab, you need to establish a second cron job which runs, say, every hour. Create hourly.sh, as shown below. Store this script in the commands directory. --- hourly.sh --- #!/bin/sh PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/local/integrity/bin webjob -e -f /root/stdout.cfg crontab.root.pad \ \( echo "\"Checking root's crontab...\"" 1\>\&2 \&\& crontab -l \| diff -uP %payload - \) \|\| \ \( echo "\"Updating root's crontab...\"" 1\>\&2 \&\& crontab %payload \) --- hourly.sh --- sed -e '1,/^--- hourly.sh ---$/d; /^--- hourly.sh ---$/,$d' webjob-pad-crontab.txt > hourly.sh install -m 644 -o root -g wheel hourly.sh /integrity/profiles/common/commands When it's time to configure the various client systems, you should check their version of crontab. Older versions of crontab may cause the diff test to fail because they insert an automatically generated header into the crontab output -- look at the example output below. Consequently, the managed crontab will be updated every time WebJob runs. To avoid this situation, you can either upgrade your copy of crontab or filter out the header -- e.g. use 'crontab -l | tail +4' instead of 'crontab -l'. --- output --- # DO NOT EDIT THIS FILE - edit the master and reinstall. # (crontab.root installed on Wed Nov 6 02:32:46 2002) # (Cron version -- $FreeBSD: src/usr.sbin/cron/crontab/crontab.c,v 1.12.2.2 2000/12/11 01:03:31 obrien Exp $) 0 1 * * * echo "This cron job was executed on `hostname` at `date`" --- output --- The server should now have a tree similar to the one shown here: integrity | - incoming | - logfiles | - profiles | - | - | - ... | - common | - commands | | | - crontab.root.pad | - hourly.sh | - crontabs | - crontab.root 5. At this point, the WebJob server has been configured. Now, we turn our attention to the clients. Create two WebJob config files, upload.cfg and stdout.cfg, on each managed client in root's home directory. The only difference between these files is that URLPutURL is defined in upload.cfg but not in stdout.cfg. Here's an example: --- upload.cfg --- ClientId=client_0001 URLGetURL=https://your.server.net/cgi-webjob/nph-webjob.cgi URLPutURL=https://your.server.net/cgi-webjob/nph-webjob.cgi URLUsername=client_0001 URLPassword=password URLAuthType=basic RunType=snapshot OverwriteExecutable=Y UnlinkOutput=Y UnlinkExecutable=Y GetTimeLimit=0 RunTimeLimit=0 PutTimeLimit=0 URLDownloadLimit=10000000 TempDirectory=/root --- upload.cfg --- sed -e '1,/^--- upload.cfg ---$/d; /^--- upload.cfg ---$/,$d' webjob-pad-crontab.txt > upload.cfg install -m 600 -o root -g wheel upload.cfg /root --- stdout.cfg --- ClientId=client_0001 URLGetURL=https://your.server.net/cgi-webjob/nph-webjob.cgi URLUsername=client_0001 URLPassword=password URLAuthType=basic RunType=snapshot OverwriteExecutable=Y UnlinkOutput=Y UnlinkExecutable=Y GetTimeLimit=0 RunTimeLimit=0 PutTimeLimit=0 URLDownloadLimit=10000000 TempDirectory=/root --- stdout.cfg --- sed -e '1,/^--- stdout.cfg ---$/d; /^--- stdout.cfg ---$/,$d' webjob-pad-crontab.txt > stdout.cfg install -m 600 -o root -g wheel stdout.cfg /root 6. Confirm that root's crontab doesn't already exist. If it does exist, then be warned that any jobs in this file will be overwritten by the actions taken in step eight. To check root's crontab, run the following command as root: crontab -l This should produce the following output: --- output --- crontab: no crontab for root --- output --- 7. At this point, you are ready to test everything out. Log into one client system and run the following command: /usr/local/integrity/bin/webjob -e -f /root/upload.cfg hourly.sh Once the command completes, you should find four files in the server's incoming directory. The files listed here will give you an idea of what to look for: client_0001_20021105180122_hourly.sh.env client_0001_20021105180122_hourly.sh.err client_0001_20021105180122_hourly.sh.out client_0001_20021105180122_hourly.sh.rdy Inspect these files. Their content should be similar to that shown below. The .rdy file is simply a lock release mechanism. Therefore, it's content has not been listed. --- client_0001_20021105180122_hourly.sh.env --- CommandLine=hourly.sh JobPid=39605 KidPid=39609 KidStatus=0 KidSignal=0 KidReason=The kid exited cleanly. Hostname=your.server.net SystemOS=i386 FreeBSD 4.6.1-RELEASE-p10 JobEpoch=2002-11-05 23:00:00 GMT (1036537200.427407) GetEpoch=2002-11-05 23:00:00 GMT (1036537200.459162) RunEpoch=2002-11-05 23:00:00 GMT (1036537200.655236) PutEpoch=2002-11-05 23:00:00 GMT (1036537200.973044) --- client_0001_20021105180122_hourly.sh.env --- --- client_0001_20021105180122_hourly.sh.err --- Extracting payload... Delivering payload... ( echo "Checking root's crontab..." 1>&2 && crontab -l | diff -uP crontab.root - ) || ( echo "Updating root's crontab..." 1>&2 && crontab crontab.root ) Checking root's crontab... crontab: no crontab for root Updating root's crontab... DeliveryStatus='0' --- client_0001_20021105180122_hourly.sh.err --- --- client_0001_20021105180122_hourly.sh.out --- --- crontab.root Tue Nov 5 18:01:22 2002 +++ - Tue Nov 5 18:01:22 2002 @@ -1 +0,0 @@ -0 1 * * * echo "This cron job was executed on `hostname` at `date`" --- client_0001_20021105180122_hourly.sh.out --- 8. Once you are satisfied that all is well, add the following cron job to each client's primary crontab. --- crontab.hourly --- 0 * * * * root /usr/local/integrity/bin/webjob -e -f /root/upload.cfg hourly.sh > /dev/null 2>&1 --- crontab.hourly --- From this point forward, you should be able to manage root's crontab from one central location. This would be done, for example, by editing crontab.root and creating a new PaD script. The next time each host runs their hourly cron job, a change would be detected by diff, and consequently, the new crontab would be installed. As an added benefit, the diff output preserved in the WebJob upload can be used as a negative or reverse patch. Applying this patch to the crontab extracted from the PaD file allows you to reconstruct the original crontab as deployed. Thus, there is an inherit quarantining property built into this scheme.