oreilly.comSafari Books Online.Conferences.


Distributed Cfengine

by Luke A. Kanies

In this article we are going to take the script we wrote in Introducing Cfengine and distribute it to all of our servers using cfengine. As an added bonus, we're going to pull both our cfengine configuration and the sudoers file directly out of a versioning system. It's a simple additional step — something you should do with all centralized configuration files — and provides a convenient control point for modifying and auditing your configurations.

The goal here is to allow us to commit changes to our sudoers files in one place and have those changes automatically propagated to all of our servers. You check the file out into your CVS sandbox, make your changes, and commit them, and the system will handle publishing those changes. We'll modify and distribute our cfengine configuration via the same mechanisms, which will allow us to expand the cfengine configuration as necessary without worrying about how to distribute the enhanced configuration.

Copying Our Configuration

There are two sides to every distribution mechanism: the client and server. The cfagent binary is the client side of cfengine and cfservd functions as the server. Cfengine has multiple layers of security checks, which results in a somewhat onerous transport setup. Unfortunately (but not surprisingly, given the difficulty of doing so), cfengine also does not always provide relevant error codes when something goes wrong.

To distribute our configurations we have to configure both the client and the server. For cfengine to run automatically we have to configure cfexecd, a simple wrapper for cfagent that sends output to system administrators via email.

Before we start that, though, let's put all of our files into CVS, since it's easy and important. It's probably possible to centralize configurations without a version control system, but I certainly would not want to try it. Since most versioning systems were created for developers, we aren't likely to use all of their features, which makes this task even easier.

I use CVS here because it's a de facto standard and is easy to use; the example should work with little modification with any other versioning system. This article assumes a working knowledge of CVS and a functional CVS repository. If you do not have these, CVS Home can likely provide any necessary help.

Version-Controlling Your Configuration

We're going to start with the configuration for cfengine itself, then add the sudoers file once we have cfengine working. It's unlikely that the examples here will mesh perfectly with an existing repository, so expect to modify them if you are already versioning files. Given that, let's jump right in.

Create a temporary directory for your cfengine files, and create three files in it:

~ $ mkdir /tmp/cfinit/inputs
~ $ cd /tmp/cfinit/inputs
/tmp/cfinit/inputs $ touch cfagent.conf cfservd.conf update.conf
/tmp/cfinit/inputs $ cp /tmp/ .

Notice we've included the configuration we created in the previous article. We're creating these files in an inputs subdirectory; as our configuration grows more sophisticated, we'll add other cfengine files, such as modules and data files, and we need to support them now so we don't have to change our system later.

Import those files into an appropriate CVS module:

~ $ cd /tmp/cfinit
/tmp/cfinit $ cvs import -m "new cfengine configuration" \
config/cfengine LAK yay

The last two arguments to cvs import are generally not useful to system administrators. I use my initials for the second argument and a somewhat random term for the third argument; I have not yet used either of these arguments for anything meaningful once I have imported something.

Finally, put our sudoers file into CVS:

~ $ mkdir /tmp/sudo
~ $ cp /etc/sudoers /tmp/sudo
~ $ cd /tmp/sudo
/tmp/sudo $ cvs import -m "distributing" config/sudo LAK init

We've put it into the same tree as cfengine, so it will be easy to update both with a single CVS command.

Now decide where you want to store those files so that cfservd can distribute them. I generally pick a location appropriate for the given site and then link it back to /cfengine, which makes it easy to move things around if necessary.

~ $ mkdir /export/cfengine
~ $ cd /export/cfengine
/export/cfengine $ cvs checkout config
/export/cfengine $ ln -s /export/cfengine /cfengine

We've now laid the groundwork for distribution, in that we have all of the files centralized in a versioning system and we have them checked out into a central location from which clients can retrieve them. Our versioning system functions as a kind of funnel here, with all changes passing from people's sandboxes through the CVS repository and into the checked out store on the server. This eliminates most problems with two people modifying the same file and provides auditing and historical functions.

Since the cfengine configuration files that we've checked into CVS are empty, we need to check them out in a CVS sandbox to allow us to edit them:

~ $ mkdir cvs
~ $ cd ~/cvs
~/cvs $ cvs checkout config

A Note About Keys

Related Reading

CVS Pocket Reference
By Gregor N. Purdy

Cfengine uses public/private key pairs similar to SSH's key pairs. Unlike SSH, which usually only has a key pair for the server, cfengine requires that both sides of a connection trust the other side. This means that for a cfengine connection to work, each side of the connection must have the public key for the other side. Also unlike SSH, cfengine does not normally run interactively, which means that there's no prompt for whether you want to accept a public key from a new host. There are several ways to work around this problem, but they mostly come down to one of two solutions: copying public keys manually, or explicitly trust the IP addresses of the clients, servers, or both.

It is possible to use another cfengine tool, cfrun, to retrieve a public key for a host manually. This can be an acceptable method for some cases, but it requires extra setup and, being an interactive utility, does not solve the automation problem. Hopefully we will discuss cfrun in a later article.

One important note about explicitly trusting an IP address is that it is only ever used on the first connection. Once you've retrieved the public key for a machine through a trusted connection, trust is never used again. This means that if a machine's keys change, you must manually update the keys; you cannot use trust to do so.

I prefer solving the problem of key management with a combination of trust and manual intervention. Because my server's public key should never change, I distribute that key with my cfengine package. Use cfkey to create the key pair. Once you've run it on your server, distribute /var/cfengine/ppkeys/ from your server to your clients as /var/cfengine/ppkeys/, assuming is your server's IP address.

Add this public key to your CVS repository:

~/cvs/config/cfengine $ mkdir ppkeys
~/cvs/config/cfengine $ cvs add ppkeys
~/cvs/config/cfengine $ sudo cp /var/cfengine/ppkeys/ \
~/cvs/config/cfengine $ sudo chown $LOGNAME ppkeys/root-*
~/cvs/config/cfengine $ cvs add ppkeys/root-*

This will distribute our server's public key with our configuration, and sets an example for distributing other servers' public keys. Ironically, this is a mostly pointless exercise because I can't pull this file down unless trust is already set up and I can't trust without this key. It makes me feel better, though, so I do it anyway.

Now that our client can verify the authenticity of our server, we just need to tell our server about the client. We're going to use trust for this end of the connection, because this is a simple example. This trust relationship is configured using the TrustKeysFrom directive in cfservd.conf. In most real-world configurations, I collect the client key as part of my cfengine bootstrap process. I hope to delineate this process in a later article focusing on bootstrapping cfengine.

Pages: 1, 2, 3

Next Pagearrow

Sponsored by: