oreilly.comSafari Books Online.Conferences.


Distributed Cfengine
Pages: 1, 2, 3

Actually Doing Something

Whew! Now we should be successfully distributing our empty cfagent.conf, so we can move on to doing something within this configuration. Remember that the update.conf file is purely for updating the cfengine configuration, so we must start within cfagent.conf for anything else.

We've already added our file to CVS, so now let's import that file into our configuration:

# cfagent.conf
	# the name of our server is 'server'
	cfengine_server = ( server )

    domain = ( ExecResult(/bin/domainname) )
    workdir = ( /var/cfengine )
    configroot = ( /cfengine )
    server = ( )


Hmmm, that's annoying, we seem to have duplicate definitions of variables here. update.conf is completely separate from the rest of the cfengine configuration. This is intentional; if you break any other aspect of your cfengine configuration, you can fix it by updating from the central copy, but if you break update.conf, you've broken the update process itself. Keep this file as simple as possible; all information collected in this file is expunged before the normal configuration is executed. You may not consider this a feature, but the author of cfengine certainly does.

When this script runs on the cfengine server, it imports two files. For the clients all we do initially is import our sudo configuration. We need to modify our file to make it copy the sudoers file from the central server, rather than just enforcing permissions. This is the whole purpose of our article.

    actionsequence = ( files copy )

    /usr/local/bin/sudo owner=root group=root mode=4111
        checksum=md5 action=fixall

    ${configroot}/config/sudo/sudoers    dest=/etc/sudoers

This is almost exactly the file we built in the first article in this series, but our files action has become a copy action. Now instead of verifying only the permissions of the sudoers file, we're updating it from a central, version-controlled location. This is not much different, but is much more functional (and requires just a bit more setup) than our original version. We still verify permissions on both the binary and the configuration file, but we now have the ability to commit modifications to our sudoers file into CVS and have those changes distributed to all of our clients.

Note that you can also configure CVS to verify the syntax of the sudoers file so that you never accidentally distribute an invalid file. This is done entirely within CVS, though, so it's left as an exercise for the reader. You could also use the above script to distribute the sudo binary itself, but the assumption here is that you've already installed the package when the system was built. Copying binaries quickly gets complicated if you're dealing with multiple platforms.

Updating CVS

Now that we have our complete sudo configuration and we are successfully updating it from the checked out copy on the server, it's time to see how the server gets the most recent version of the file. As your configurations get more complicated, this simple setup will likely not suffice, but this works well for getting started:

    actionsequence = ( shellcommands )

    "/bin/sh -c 'cd /cfengine; cvs update -d >/dev/null 2>/dev/null'"

This file, imported by the cfengine server from within cfagent.conf, introduces the shellcommands action. As you can see, this is a very simple action. There are some other options you can use, but this is how shellcommands instances usually look. Notice that we had to use an explicit subshell to use cd; when cfengine runs a shell for you, it never interprets shell metacharacters, so if you want the shell to interpret characters such as >, ;, or |, you have to launch a subshell explicitly, as above.

Figure 1 shows how data travels from your CVS sandbox to remote servers. Assuming that cfengine runs every 30 minutes, the potential delays mean that it can take up to 90 minutes for a CVS change to propagate completely.

Figure 1
Figure 1. How data travels from your CVS sandbox to remote servers.

This is a simple method of having cfengine use cvs to update its files. A more sophisticated and less error prone form of this would use a cfengine module that did error checking. As this stands, any errors go to /dev/null (because cvs produces output on STDOUT and STDERR) which means you are not likely to notice a problem quickly. If you did not redirect the output of cvs here, you would get an email every time cfengine ran, which would quickly cause you to ignore all cfengine emails.

Finishing Touches

While that may have seemed like a lot of work, we now have a solid groundwork for using cfengine as the automation harness for the rest of our network tasks. It's now as simple as modifying files and committing them to CVS. Cfengine might not be able to do everything we need it to, but it can at least function as the logic and initiation engine for most everything else. If you have recreated this configuration on your own site, you should now be able to use cfengine to distribute newly committed versions of the sudoers file and to verify that sudo is always set up correctly. All you need to do for this to work all the time is add a cron job to execute it periodically.

Although cfengine is perfectly capable of adding that cron job for you, there are some subtle and complicated issues in doing so, which makes it the perfect topic for the next article in this series. Next time we'll cover inline editing of files using cfengine and how to monitor and restart processes based on changes that cfengine makes. We'll also introduce the use of cfexecd to wrap our call to cfagent in order to get some handling of cfagent output. In the meantime, you can find these examples in CVS at, and you can find multiple examples on how to add a cron job using cfengine at the Cfengine Homepage.

Luke A. Kanies is an independent consultant and researcher specializing in Unix automation and configuration management.

Return to

Sponsored by: